blob: eb41f01c29c41813757403d740d67cd8d7d63c9d [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
34def _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
49def _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
60def _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
70def _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
95def _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
104def _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
109def _GetCreateFuncName(interface):
110 """Returns the creation function name for an interface."""
111 return 'Create%s' % _GetShortName(interface, ['Dev'])
112
113
114def _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
133def _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
165def _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
223def 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
252class 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, cpp_style=True))
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
378tgen = TGen()
379
380
381def 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
402if __name__ == '__main__':
403 sys.exit(Main(sys.argv[1:]))