blob: 250054ee4a5053fa4a301dfdd91284670a488e8f [file] [log] [blame]
[email protected]8c311f02012-11-17 16:01:321#!/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
8import glob
9import os
10import re
11import sys
12
13from idl_log import ErrOut, InfoOut, WarnOut
14from idl_node import IDLAttribute, IDLNode
15from idl_ast import IDLAst
16from idl_option import GetOption, Option, ParseOptions
17from idl_outfile import IDLOutFile
18from idl_parser import ParseFiles
19from idl_c_proto import CGen, GetNodeComments, CommentLines, Comment
20from idl_generator import Generator, GeneratorByFile
21
22Option('thunkroot', 'Base directory of output',
23 default=os.path.join('..', 'thunk'))
24
25
26class TGenError(Exception):
27 def __init__(self, msg):
28 self.value = msg
29
30 def __str__(self):
31 return repr(self.value)
32
33
[email protected]cba2c4302012-11-20 18:18:1334class ThunkBodyMetadata(object):
35 """Metadata about thunk body. Used for selecting which headers to emit."""
36 def __init__(self):
37 self._apis = set()
[email protected]8f2a08d52012-12-08 00:10:5038 self._includes = set()
[email protected]cba2c4302012-11-20 18:18:1339
40 def AddApi(self, api):
41 self._apis.add(api)
42
43 def Apis(self):
44 return self._apis
45
[email protected]8f2a08d52012-12-08 00:10:5046 def AddInclude(self, include):
47 self._includes.add(include)
48
49 def Includes(self):
50 return self._includes
51
[email protected]cba2c4302012-11-20 18:18:1352
[email protected]8c311f02012-11-17 16:01:3253def _GetBaseFileName(filenode):
54 """Returns the base name for output files, given the filenode.
55
56 Examples:
57 'dev/ppb_find_dev.h' -> 'ppb_find'
58 'trusted/ppb_buffer_trusted.h' -> 'ppb_buffer_trusted'
59 """
60 path, name = os.path.split(filenode.GetProperty('NAME'))
61 name = os.path.splitext(name)[0]
62 if name.endswith('_dev'):
63 # Clip off _dev suffix.
64 name = name[:-len('_dev')]
65 return name
66
67
68def _GetHeaderFileName(filenode):
69 """Returns the name for the header for this file."""
70 path, name = os.path.split(filenode.GetProperty('NAME'))
71 name = os.path.splitext(name)[0]
72 if path:
73 header = "ppapi/c/%s/%s.h" % (path, name)
74 else:
75 header = "ppapi/c/%s.h" % name
76 return header
77
78
79def _GetThunkFileName(filenode, relpath):
80 """Returns the thunk file name."""
81 path = os.path.split(filenode.GetProperty('NAME'))[0]
82 name = _GetBaseFileName(filenode)
83 # We don't reattach the path for thunk.
84 if relpath: name = os.path.join(relpath, name)
85 name = '%s%s' % (name, '_thunk.cc')
86 return name
87
88
[email protected]cba2c4302012-11-20 18:18:1389def _MakeEnterLine(filenode, interface, arg, handle_errors, callback, meta):
[email protected]8c311f02012-11-17 16:01:3290 """Returns an EnterInstance/EnterResource string for a function."""
91 if arg[0] == 'PP_Instance':
92 if callback is None:
93 return 'EnterInstance enter(%s);' % arg[1]
94 else:
95 return 'EnterInstance enter(%s, %s);' % (arg[1], callback)
96 elif arg[0] == 'PP_Resource':
97 api_name = interface.GetName()
98 if api_name.endswith('_Dev'):
99 api_name = api_name[:-len('_Dev')]
100 api_name += '_API'
101
102 enter_type = 'EnterResource<%s>' % api_name
[email protected]13cc22f32012-11-21 18:04:12103 # The API header matches the file name, not the interface name.
104 meta.AddApi(_GetBaseFileName(filenode) + '_api')
105
[email protected]8c311f02012-11-17 16:01:32106 if callback is None:
107 return '%s enter(%s, %s);' % (enter_type, arg[1],
108 str(handle_errors).lower())
109 else:
110 return '%s enter(%s, %s, %s);' % (enter_type, arg[1],
111 callback,
112 str(handle_errors).lower())
113 else:
114 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg[0])
115
116
117def _GetShortName(interface, filter_suffixes):
118 """Return a shorter interface name that matches Is* and Create* functions."""
119 parts = interface.GetName().split('_')[1:]
120 tail = parts[len(parts) - 1]
121 if tail in filter_suffixes:
122 parts = parts[:-1]
123 return ''.join(parts)
124
125
126def _IsTypeCheck(interface, node):
127 """Returns true if node represents a type-checking function."""
128 return node.GetName() == 'Is%s' % _GetShortName(interface, ['Dev', 'Private'])
129
130
131def _GetCreateFuncName(interface):
132 """Returns the creation function name for an interface."""
133 return 'Create%s' % _GetShortName(interface, ['Dev'])
134
135
136def _GetDefaultFailureValue(t):
137 """Returns the default failure value for a given type.
138
139 Returns None if no default failure value exists for the type.
140 """
141 values = {
142 'PP_Bool': 'PP_FALSE',
143 'PP_Resource': '0',
144 'struct PP_Var': 'PP_MakeUndefined()',
145 'int32_t': 'enter.retval()',
146 'uint16_t': '0',
147 'uint32_t': '0',
148 'uint64_t': '0',
149 }
150 if t in values:
151 return values[t]
152 return None
153
154
155def _MakeCreateMemberBody(interface, member, args):
156 """Returns the body of a Create() function.
157
158 Args:
159 interface - IDLNode for the interface
160 member - IDLNode for member function
161 args - List of arguments for the Create() function
162 """
163 if args[0][0] == 'PP_Resource':
164 body = ' Resource* object =\n'
165 body += ' PpapiGlobals::Get()->GetResourceTracker()->'
166 body += 'GetResource(%s);\n' % args[0][1]
167 body += ' if (!object)\n'
168 body += ' return 0;\n'
169 body += ' EnterResourceCreation enter(object->pp_instance());\n'
170 elif args[0][0] == 'PP_Instance':
171 body = ' EnterResourceCreation enter(%s);\n' % args[0][1]
172 else:
173 raise TGenError('Unknown arg type for Create(): %s' % args[0][0])
174
175 body += ' if (enter.failed())\n'
176 body += ' return 0;\n'
177 arg_list = ', '.join([a[1] for a in args])
178 if member.GetProperty('create_func'):
179 create_func = member.GetProperty('create_func')
180 else:
181 create_func = _GetCreateFuncName(interface)
182 body += ' return enter.functions()->%s(%s);' % (create_func,
183 arg_list)
184 return body
185
186
[email protected]cba2c4302012-11-20 18:18:13187def _MakeNormalMemberBody(filenode, node, member, rtype, args, meta):
[email protected]8c311f02012-11-17 16:01:32188 """Returns the body of a typical function.
189
190 Args:
191 filenode - IDLNode for the file
192 node - IDLNode for the interface
193 member - IDLNode for the member function
194 rtype - Return type for the member function
195 args - List of 4-tuple arguments for the member function
[email protected]cba2c4302012-11-20 18:18:13196 meta - ThunkBodyMetadata for header hints
[email protected]8c311f02012-11-17 16:01:32197 """
198 is_callback_func = args[len(args) - 1][0] == 'struct PP_CompletionCallback'
199
200 if is_callback_func:
201 call_args = args[:-1] + [('', 'enter.callback()', '', '')]
[email protected]8f2a08d52012-12-08 00:10:50202 meta.AddInclude('ppapi/c/pp_completion_callback.h')
[email protected]8c311f02012-11-17 16:01:32203 else:
204 call_args = args
205
206 if args[0][0] == 'PP_Instance':
207 call_arglist = ', '.join(a[1] for a in call_args)
208 function_container = 'functions'
209 else:
210 call_arglist = ', '.join(a[1] for a in call_args[1:])
211 function_container = 'object'
212
213 invocation = 'enter.%s()->%s(%s)' % (function_container,
214 member.GetName(),
215 call_arglist)
216
217 handle_errors = not (member.GetProperty('report_errors') == 'False')
218 if is_callback_func:
219 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
[email protected]cba2c4302012-11-20 18:18:13220 args[len(args) - 1][1], meta)
[email protected]8c311f02012-11-17 16:01:32221 body += ' if (enter.failed())\n'
222 value = member.GetProperty('on_failure')
223 if value is None:
224 value = 'enter.retval()'
225 body += ' return %s;\n' % value
[email protected]324eec72012-12-07 01:12:52226 body += ' return enter.SetResult(%s);' % invocation
[email protected]8c311f02012-11-17 16:01:32227 elif rtype == 'void':
228 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
[email protected]cba2c4302012-11-20 18:18:13229 None, meta)
[email protected]8c311f02012-11-17 16:01:32230 body += ' if (enter.succeeded())\n'
231 body += ' %s;' % invocation
232 else:
233 value = member.GetProperty('on_failure')
234 if value is None:
235 value = _GetDefaultFailureValue(rtype)
236 if value is None:
237 raise TGenError('No default value for rtype %s' % rtype)
238
239 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], handle_errors,
[email protected]cba2c4302012-11-20 18:18:13240 None, meta)
[email protected]8c311f02012-11-17 16:01:32241 body += ' if (enter.failed())\n'
242 body += ' return %s;\n' % value
243 body += ' return %s;' % invocation
244 return body
245
246
[email protected]cba2c4302012-11-20 18:18:13247def DefineMember(filenode, node, member, release, include_version, meta):
[email protected]8c311f02012-11-17 16:01:32248 """Returns a definition for a member function of an interface.
249
250 Args:
251 filenode - IDLNode for the file
252 node - IDLNode for the interface
253 member - IDLNode for the member function
254 release - release to generate
255 include_version - include the version in emitted function name.
[email protected]cba2c4302012-11-20 18:18:13256 meta - ThunkMetadata for header hints
[email protected]8c311f02012-11-17 16:01:32257 Returns:
258 A string with the member definition.
259 """
260 cgen = CGen()
261 rtype, name, arrays, args = cgen.GetComponents(member, release, 'return')
262
263 if _IsTypeCheck(node, member):
[email protected]cba2c4302012-11-20 18:18:13264 body = ' %s\n' % _MakeEnterLine(filenode, node, args[0], False, None, meta)
[email protected]8c311f02012-11-17 16:01:32265 body += ' return PP_FromBool(enter.succeeded());'
266 elif member.GetName() == 'Create':
267 body = _MakeCreateMemberBody(node, member, args)
268 else:
[email protected]cba2c4302012-11-20 18:18:13269 body = _MakeNormalMemberBody(filenode, node, member, rtype, args, meta)
[email protected]8c311f02012-11-17 16:01:32270
271 signature = cgen.GetSignature(member, release, 'return', func_as_ptr=False,
272 include_version=include_version)
273 member_code = '%s {\n%s\n}' % (signature, body)
274 return cgen.Indent(member_code, tabs=0)
275
276
277class TGen(GeneratorByFile):
278 def __init__(self):
279 Generator.__init__(self, 'Thunk', 'tgen', 'Generate the C++ thunk.')
280
281 def GenerateFile(self, filenode, releases, options):
282 savename = _GetThunkFileName(filenode, GetOption('thunkroot'))
283 my_min, my_max = filenode.GetMinMax(releases)
284 if my_min > releases[-1] or my_max < releases[0]:
285 if os.path.isfile(savename):
286 print "Removing stale %s for this range." % filenode.GetName()
287 os.remove(os.path.realpath(savename))
288 return False
289 do_generate = filenode.GetProperty('generate_thunk')
290 if not do_generate:
291 return False
292
293 thunk_out = IDLOutFile(savename)
[email protected]cba2c4302012-11-20 18:18:13294 body, meta = self.GenerateBody(thunk_out, filenode, releases, options)
295 self.WriteHead(thunk_out, filenode, releases, options, meta)
296 thunk_out.Write('\n\n'.join(body))
297 self.WriteTail(thunk_out, filenode, releases, options)
[email protected]8c311f02012-11-17 16:01:32298 return thunk_out.Close()
299
[email protected]cba2c4302012-11-20 18:18:13300 def WriteHead(self, out, filenode, releases, options, meta):
[email protected]8c311f02012-11-17 16:01:32301 __pychecker__ = 'unusednames=options'
302 cgen = CGen()
303
304 cright_node = filenode.GetChildren()[0]
305 assert(cright_node.IsA('Copyright'))
306 out.Write('%s\n' % cgen.Copyright(cright_node, cpp_style=True))
307
308 # Wrap the From ... modified ... comment if it would be >80 characters.
309 from_text = 'From %s' % (
310 filenode.GetProperty('NAME').replace(os.sep,'/'))
311 modified_text = 'modified %s.' % (
312 filenode.GetProperty('DATETIME'))
313 if len(from_text) + len(modified_text) < 74:
314 out.Write('// %s %s\n\n' % (from_text, modified_text))
315 else:
316 out.Write('// %s,\n// %s\n\n' % (from_text, modified_text))
317
318
319 # TODO(teravest): Don't emit includes we don't need.
320 includes = ['ppapi/c/pp_errors.h',
321 'ppapi/shared_impl/tracked_callback.h',
322 'ppapi/thunk/enter.h',
323 'ppapi/thunk/ppb_instance_api.h',
324 'ppapi/thunk/resource_creation_api.h',
325 'ppapi/thunk/thunk.h']
326 includes.append(_GetHeaderFileName(filenode))
[email protected]cba2c4302012-11-20 18:18:13327 for api in meta.Apis():
328 includes.append('ppapi/thunk/%s.h' % api.lower())
[email protected]8f2a08d52012-12-08 00:10:50329 for i in meta.Includes():
330 includes.append(i)
[email protected]8c311f02012-11-17 16:01:32331 for include in sorted(includes):
332 out.Write('#include "%s"\n' % include)
333 out.Write('\n')
334 out.Write('namespace ppapi {\n')
335 out.Write('namespace thunk {\n')
336 out.Write('\n')
337 out.Write('namespace {\n')
338 out.Write('\n')
339
340 def GenerateBody(self, out, filenode, releases, options):
[email protected]cba2c4302012-11-20 18:18:13341 """Generates a member function lines to be written and metadata.
342
343 Returns a tuple of (body, meta) where:
344 body - a list of lines with member function bodies
345 meta - a ThunkMetadata instance for hinting which headers are needed.
346 """
[email protected]8c311f02012-11-17 16:01:32347 __pychecker__ = 'unusednames=options'
[email protected]cba2c4302012-11-20 18:18:13348 members = []
349 meta = ThunkBodyMetadata()
[email protected]8c311f02012-11-17 16:01:32350 for node in filenode.GetListOf('Interface'):
351 # Skip if this node is not in this release
352 if not node.InReleases(releases):
353 print "Skipping %s" % node
354 continue
355
356 # Generate Member functions
357 if node.IsA('Interface'):
[email protected]8c311f02012-11-17 16:01:32358 for child in node.GetListOf('Member'):
359 build_list = child.GetUniqueReleases(releases)
360 # We have to filter out releases this node isn't in.
361 build_list = filter(lambda r: child.InReleases([r]), build_list)
362 if len(build_list) == 0:
363 continue
364 release = build_list[-1] # Pick the newest release.
[email protected]cba2c4302012-11-20 18:18:13365 member = DefineMember(filenode, node, child, release, False, meta)
[email protected]8c311f02012-11-17 16:01:32366 if not member:
367 continue
368 members.append(member)
369 for build in build_list[:-1]:
[email protected]cba2c4302012-11-20 18:18:13370 member = DefineMember(filenode, node, child, build, True, meta)
[email protected]8c311f02012-11-17 16:01:32371 if not member:
372 continue
373 members.append(member)
[email protected]cba2c4302012-11-20 18:18:13374 return (members, meta)
[email protected]8c311f02012-11-17 16:01:32375
[email protected]cba2c4302012-11-20 18:18:13376 def WriteTail(self, out, filenode, releases, options):
[email protected]8c311f02012-11-17 16:01:32377 __pychecker__ = 'unusednames=options'
378 cgen = CGen()
379
380 version_list = []
381 out.Write('\n\n')
382 for node in filenode.GetListOf('Interface'):
383 build_list = node.GetUniqueReleases(releases)
384 for build in build_list:
385 version = node.GetVersion(build).replace('.', '_')
386 thunk_name = 'g_' + node.GetName().lower() + '_thunk_' + \
387 version
388 thunk_type = '_'.join((node.GetName(), version))
389 version_list.append((thunk_type, thunk_name))
390
391 out.Write('const %s %s = {\n' % (thunk_type, thunk_name))
392 for child in node.GetListOf('Member'):
393 rtype, name, arrays, args = cgen.GetComponents(
394 child, build, 'return')
395 if child.InReleases([build]): # TEST
396 out.Write(' &%s,\n' % name)
397 out.Write('};\n\n')
398
399 out.Write('} // namespace\n')
400 out.Write('\n')
401 for thunk_type, thunk_name in version_list:
402 thunk_decl = 'const %s* Get%s_Thunk() {\n' % (thunk_type, thunk_type)
403 if len(thunk_decl) > 80:
404 thunk_decl = 'const %s*\n Get%s_Thunk() {\n' % (thunk_type,
405 thunk_type)
406 out.Write(thunk_decl)
407 out.Write(' return &%s;\n' % thunk_name)
408 out.Write('}\n')
409 out.Write('\n')
410 out.Write('} // namespace thunk\n')
411 out.Write('} // namespace ppapi\n')
412
413
414tgen = TGen()
415
416
417def Main(args):
418 # Default invocation will verify the golden files are unchanged.
419 failed = 0
420 if not args:
421 args = ['--wnone', '--diff', '--test', '--thunkroot=.']
422
423 ParseOptions(args)
424
425 idldir = os.path.split(sys.argv[0])[0]
426 idldir = os.path.join(idldir, 'test_thunk', '*.idl')
427 filenames = glob.glob(idldir)
428 ast = ParseFiles(filenames)
429 if tgen.GenerateRange(ast, ['M13', 'M14'], {}):
430 print "Golden file for M13-M14 failed."
431 failed = 1
432 else:
433 print "Golden file for M13-M14 passed."
434
435 return failed
436
437
438if __name__ == '__main__':
439 sys.exit(Main(sys.argv[1:]))