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.
6 """ Generator for C++ style thunks """
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
22 Option('thunkroot', 'Base directory of output',
23 default
=os
.path
.join('..', 'thunk'))
26 class TGenError(Exception):
27 def __init__(self
, msg
):
31 return repr(self
.value
)
34 class ThunkBodyMetadata(object):
35 """Metadata about thunk body. Used for selecting which headers to emit."""
38 self
._includes
= set()
40 def AddApi(self
, api
):
46 def AddInclude(self
, include
):
47 self
._includes
.add(include
)
53 def _GetBaseFileName(filenode
):
54 """Returns the base name for output files, given the filenode.
57 'dev/ppb_find_dev.h' -> 'ppb_find_dev'
58 'trusted/ppb_buffer_trusted.h' -> 'ppb_buffer_trusted'
60 path
, name
= os
.path
.split(filenode
.GetProperty('NAME'))
61 name
= os
.path
.splitext(name
)[0]
65 def _GetHeaderFileName(filenode
):
66 """Returns the name for the header for this file."""
67 path
, name
= os
.path
.split(filenode
.GetProperty('NAME'))
68 name
= os
.path
.splitext(name
)[0]
70 header
= "ppapi/c/%s/%s.h" % (path
, name
)
72 header
= "ppapi/c/%s.h" % name
76 def _GetThunkFileName(filenode
, relpath
):
77 """Returns the thunk file name."""
78 path
= os
.path
.split(filenode
.GetProperty('NAME'))[0]
79 name
= _GetBaseFileName(filenode
)
80 # We don't reattach the path for thunk.
81 if relpath
: name
= os
.path
.join(relpath
, name
)
82 name
= '%s%s' % (name
, '_thunk.cc')
86 def _MakeEnterLine(filenode
, interface
, arg
, handle_errors
, callback
, meta
):
87 """Returns an EnterInstance/EnterResource string for a function."""
88 if arg
[0] == 'PP_Instance':
90 return 'EnterInstance enter(%s);' % arg
[1]
92 return 'EnterInstance enter(%s, %s);' % (arg
[1], callback
)
93 elif arg
[0] == 'PP_Resource':
94 api_name
= interface
.GetName()
95 if api_name
.endswith('Trusted'):
96 api_name
= api_name
[:-len('Trusted')]
97 if api_name
.endswith('_Dev'):
98 api_name
= api_name
[:-len('_Dev')]
101 enter_type
= 'EnterResource<%s>' % api_name
102 # The API header matches the file name, not the interface name.
103 api_basename
= _GetBaseFileName(filenode
)
104 if api_basename
.endswith('_dev'):
105 # Clip off _dev suffix.
106 api_basename
= api_basename
[:-len('_dev')]
107 if api_basename
.endswith('_trusted'):
108 # Clip off _trusted suffix.
109 api_basename
= api_basename
[:-len('_trusted')]
110 meta
.AddApi(api_basename
+ '_api')
113 return '%s enter(%s, %s);' % (enter_type
, arg
[1],
114 str(handle_errors
).lower())
116 return '%s enter(%s, %s, %s);' % (enter_type
, arg
[1],
118 str(handle_errors
).lower())
120 raise TGenError("Unknown type for _MakeEnterLine: %s" % arg
[0])
123 def _GetShortName(interface
, filter_suffixes
):
124 """Return a shorter interface name that matches Is* and Create* functions."""
125 parts
= interface
.GetName().split('_')[1:]
126 tail
= parts
[len(parts
) - 1]
127 if tail
in filter_suffixes
:
129 return ''.join(parts
)
132 def _IsTypeCheck(interface
, node
):
133 """Returns true if node represents a type-checking function."""
134 return node
.GetName() == 'Is%s' % _GetShortName(interface
, ['Dev', 'Private'])
137 def _GetCreateFuncName(interface
):
138 """Returns the creation function name for an interface."""
139 return 'Create%s' % _GetShortName(interface
, ['Dev'])
142 def _GetDefaultFailureValue(t
):
143 """Returns the default failure value for a given type.
145 Returns None if no default failure value exists for the type.
148 'PP_Bool': 'PP_FALSE',
150 'struct PP_Var': 'PP_MakeUndefined()',
152 'int32_t': 'enter.retval()',
162 def _MakeCreateMemberBody(interface
, member
, args
):
163 """Returns the body of a Create() function.
166 interface - IDLNode for the interface
167 member - IDLNode for member function
168 args - List of arguments for the Create() function
170 if args
[0][0] == 'PP_Resource':
171 body
= 'Resource* object =\n'
172 body
+= ' PpapiGlobals::Get()->GetResourceTracker()->'
173 body
+= 'GetResource(%s);\n' % args
[0][1]
174 body
+= 'if (!object)\n'
175 body
+= ' return 0;\n'
176 body
+= 'EnterResourceCreation enter(object->pp_instance());\n'
177 elif args
[0][0] == 'PP_Instance':
178 body
= 'EnterResourceCreation enter(%s);\n' % args
[0][1]
180 raise TGenError('Unknown arg type for Create(): %s' % args
[0][0])
182 body
+= 'if (enter.failed())\n'
183 body
+= ' return 0;\n'
184 arg_list
= ', '.join([a
[1] for a
in args
])
185 if member
.GetProperty('create_func'):
186 create_func
= member
.GetProperty('create_func')
188 create_func
= _GetCreateFuncName(interface
)
189 body
+= 'return enter.functions()->%s(%s);' % (create_func
,
194 def _MakeNormalMemberBody(filenode
, release
, node
, member
, rtype
, args
,
195 include_version
, meta
):
196 """Returns the body of a typical function.
199 filenode - IDLNode for the file
200 release - release to generate body for
201 node - IDLNode for the interface
202 member - IDLNode for the member function
203 rtype - Return type for the member function
204 args - List of 4-tuple arguments for the member function
205 include_version - whether to include the version in the invocation
206 meta - ThunkBodyMetadata for header hints
208 is_callback_func
= args
[len(args
) - 1][0] == 'struct PP_CompletionCallback'
211 call_args
= args
[:-1] + [('', 'enter.callback()', '', '')]
212 meta
.AddInclude('ppapi/c/pp_completion_callback.h')
216 if args
[0][0] == 'PP_Instance':
217 call_arglist
= ', '.join(a
[1] for a
in call_args
)
218 function_container
= 'functions'
220 call_arglist
= ', '.join(a
[1] for a
in call_args
[1:])
221 function_container
= 'object'
223 function_name
= member
.GetName()
225 version
= node
.GetVersion(release
).replace('.', '_')
226 function_name
+= version
228 invocation
= 'enter.%s()->%s(%s)' % (function_container
,
232 handle_errors
= not (member
.GetProperty('report_errors') == 'False')
234 body
= '%s\n' % _MakeEnterLine(filenode
, node
, args
[0], handle_errors
,
235 args
[len(args
) - 1][1], meta
)
236 body
+= 'if (enter.failed())\n'
237 value
= member
.GetProperty('on_failure')
239 value
= 'enter.retval()'
240 body
+= ' return %s;\n' % value
241 body
+= 'return enter.SetResult(%s);' % invocation
242 elif rtype
== 'void':
243 body
= '%s\n' % _MakeEnterLine(filenode
, node
, args
[0], handle_errors
,
245 body
+= 'if (enter.succeeded())\n'
246 body
+= ' %s;' % invocation
248 value
= member
.GetProperty('on_failure')
250 value
= _GetDefaultFailureValue(rtype
)
252 raise TGenError('No default value for rtype %s' % rtype
)
254 body
= '%s\n' % _MakeEnterLine(filenode
, node
, args
[0], handle_errors
,
256 body
+= 'if (enter.failed())\n'
257 body
+= ' return %s;\n' % value
258 body
+= 'return %s;' % invocation
262 def DefineMember(filenode
, node
, member
, release
, include_version
, meta
):
263 """Returns a definition for a member function of an interface.
266 filenode - IDLNode for the file
267 node - IDLNode for the interface
268 member - IDLNode for the member function
269 release - release to generate
270 include_version - include the version in emitted function name.
271 meta - ThunkMetadata for header hints
273 A string with the member definition.
276 rtype
, name
, arrays
, args
= cgen
.GetComponents(member
, release
, 'return')
278 if _IsTypeCheck(node
, member
):
279 body
= '%s\n' % _MakeEnterLine(filenode
, node
, args
[0], False, None, meta
)
280 body
+= 'return PP_FromBool(enter.succeeded());'
281 elif member
.GetName() == 'Create':
282 body
= _MakeCreateMemberBody(node
, member
, args
)
284 body
= _MakeNormalMemberBody(filenode
, release
, node
, member
, rtype
, args
,
285 include_version
, meta
)
287 signature
= cgen
.GetSignature(member
, release
, 'return', func_as_ptr
=False,
288 include_version
=include_version
)
289 return '%s\n%s\n}' % (cgen
.Indent('%s {' % signature
, tabs
=0),
290 cgen
.Indent(body
, tabs
=1))
293 def _IsNewestMember(member
, members
, releases
):
294 """Returns true if member is the newest node with its name in members.
296 Currently, every node in the AST only has one version. This means that we
297 will have two sibling nodes with the same name to represent different
299 See http://crbug.com/157017 .
301 Special handling is required for nodes which share their name with others,
302 but aren't the newest version in the IDL.
305 member - The member which is checked if it's newest
306 members - The list of members to inspect
307 releases - The set of releases to check for versions in.
309 build_list
= member
.GetUniqueReleases(releases
)
310 release
= build_list
[0] # Pick the oldest release.
311 same_name_siblings
= filter(
312 lambda n
: str(n
) == str(member
) and n
!= member
, members
)
314 for s
in same_name_siblings
:
315 sibling_build_list
= s
.GetUniqueReleases(releases
)
316 sibling_release
= sibling_build_list
[0]
317 if sibling_release
> release
:
322 class TGen(GeneratorByFile
):
324 Generator
.__init
__(self
, 'Thunk', 'tgen', 'Generate the C++ thunk.')
326 def GenerateFile(self
, filenode
, releases
, options
):
327 savename
= _GetThunkFileName(filenode
, GetOption('thunkroot'))
328 my_min
, my_max
= filenode
.GetMinMax(releases
)
329 if my_min
> releases
[-1] or my_max
< releases
[0]:
330 if os
.path
.isfile(savename
):
331 print "Removing stale %s for this range." % filenode
.GetName()
332 os
.remove(os
.path
.realpath(savename
))
334 do_generate
= filenode
.GetProperty('generate_thunk')
338 thunk_out
= IDLOutFile(savename
)
339 body
, meta
= self
.GenerateBody(thunk_out
, filenode
, releases
, options
)
340 self
.WriteHead(thunk_out
, filenode
, releases
, options
, meta
)
341 thunk_out
.Write('\n\n'.join(body
))
342 self
.WriteTail(thunk_out
, filenode
, releases
, options
)
343 return thunk_out
.Close()
345 def WriteHead(self
, out
, filenode
, releases
, options
, meta
):
346 __pychecker__
= 'unusednames=options'
349 cright_node
= filenode
.GetChildren()[0]
350 assert(cright_node
.IsA('Copyright'))
351 out
.Write('%s\n' % cgen
.Copyright(cright_node
, cpp_style
=True))
353 # Wrap the From ... modified ... comment if it would be >80 characters.
354 from_text
= 'From %s' % (
355 filenode
.GetProperty('NAME').replace(os
.sep
,'/'))
356 modified_text
= 'modified %s.' % (
357 filenode
.GetProperty('DATETIME'))
358 if len(from_text
) + len(modified_text
) < 74:
359 out
.Write('// %s %s\n\n' % (from_text
, modified_text
))
361 out
.Write('// %s,\n// %s\n\n' % (from_text
, modified_text
))
364 # TODO(teravest): Don't emit includes we don't need.
365 includes
= ['ppapi/c/pp_errors.h',
366 'ppapi/shared_impl/tracked_callback.h',
367 'ppapi/thunk/enter.h',
368 'ppapi/thunk/ppb_instance_api.h',
369 'ppapi/thunk/resource_creation_api.h',
370 'ppapi/thunk/thunk.h']
371 includes
.append(_GetHeaderFileName(filenode
))
372 for api
in meta
.Apis():
373 includes
.append('ppapi/thunk/%s.h' % api
.lower())
374 for i
in meta
.Includes():
376 for include
in sorted(includes
):
377 out
.Write('#include "%s"\n' % include
)
379 out
.Write('namespace ppapi {\n')
380 out
.Write('namespace thunk {\n')
382 out
.Write('namespace {\n')
385 def GenerateBody(self
, out
, filenode
, releases
, options
):
386 """Generates a member function lines to be written and metadata.
388 Returns a tuple of (body, meta) where:
389 body - a list of lines with member function bodies
390 meta - a ThunkMetadata instance for hinting which headers are needed.
392 __pychecker__
= 'unusednames=options'
394 meta
= ThunkBodyMetadata()
395 for node
in filenode
.GetListOf('Interface'):
396 # Skip if this node is not in this release
397 if not node
.InReleases(releases
):
398 print "Skipping %s" % node
401 # Generate Member functions
402 if node
.IsA('Interface'):
403 members
= node
.GetListOf('Member')
404 for child
in members
:
405 build_list
= child
.GetUniqueReleases(releases
)
406 # We have to filter out releases this node isn't in.
407 build_list
= filter(lambda r
: child
.InReleases([r
]), build_list
)
408 if len(build_list
) == 0:
410 assert(len(build_list
) == 1)
411 release
= build_list
[-1]
412 include_version
= not _IsNewestMember(child
, members
, releases
)
413 member
= DefineMember(filenode
, node
, child
, release
, include_version
,
417 out_members
.append(member
)
418 return (out_members
, meta
)
420 def WriteTail(self
, out
, filenode
, releases
, options
):
421 __pychecker__
= 'unusednames=options'
426 for node
in filenode
.GetListOf('Interface'):
427 build_list
= node
.GetUniqueReleases(releases
)
428 for build
in build_list
:
429 version
= node
.GetVersion(build
).replace('.', '_')
430 thunk_name
= 'g_' + node
.GetName().lower() + '_thunk_' + \
432 thunk_type
= '_'.join((node
.GetName(), version
))
433 version_list
.append((thunk_type
, thunk_name
))
435 declare_line
= 'const %s %s = {' % (thunk_type
, thunk_name
)
436 if len(declare_line
) > 80:
437 declare_line
= 'const %s\n %s = {' % (thunk_type
, thunk_name
)
438 out
.Write('%s\n' % declare_line
)
439 generated_functions
= []
440 members
= node
.GetListOf('Member')
441 for child
in members
:
442 rtype
, name
, arrays
, args
= cgen
.GetComponents(
443 child
, build
, 'return')
444 if not _IsNewestMember(child
, members
, releases
):
445 version
= node
.GetVersion(build
).replace('.', '_')
446 name
+= '_' + version
447 if child
.InReleases([build
]):
448 generated_functions
.append(name
)
449 out
.Write(',\n'.join([' &%s' % f
for f
in generated_functions
]))
450 out
.Write('\n};\n\n')
452 out
.Write('} // namespace\n')
454 for thunk_type
, thunk_name
in version_list
:
455 thunk_decl
= 'const %s* Get%s_Thunk() {\n' % (thunk_type
, thunk_type
)
456 if len(thunk_decl
) > 80:
457 thunk_decl
= 'const %s*\n Get%s_Thunk() {\n' % (thunk_type
,
459 out
.Write(thunk_decl
)
460 out
.Write(' return &%s;\n' % thunk_name
)
463 out
.Write('} // namespace thunk\n')
464 out
.Write('} // namespace ppapi\n')
471 # Default invocation will verify the golden files are unchanged.
474 args
= ['--wnone', '--diff', '--test', '--thunkroot=.']
478 idldir
= os
.path
.split(sys
.argv
[0])[0]
479 idldir
= os
.path
.join(idldir
, 'test_thunk', '*.idl')
480 filenames
= glob
.glob(idldir
)
481 ast
= ParseFiles(filenames
)
482 if tgen
.GenerateRange(ast
, ['M13', 'M14'], {}):
483 print "Golden file for M13-M14 failed."
486 print "Golden file for M13-M14 passed."
491 if __name__
== '__main__':
492 sys
.exit(Main(sys
.argv
[1:]))