|
1 #!/usr/bin/env python |
|
2 # header.py - Generate C++ header files from IDL. |
|
3 # |
|
4 # This Source Code Form is subject to the terms of the Mozilla Public |
|
5 # License, v. 2.0. If a copy of the MPL was not distributed with this |
|
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/. |
|
7 |
|
8 """Print a C++ header file for the IDL files specified on the command line""" |
|
9 |
|
10 import sys, os.path, re, xpidl, itertools, glob |
|
11 |
|
12 printdoccomments = False |
|
13 |
|
14 if printdoccomments: |
|
15 def printComments(fd, clist, indent): |
|
16 for c in clist: |
|
17 fd.write("%s%s\n" % (indent, c)) |
|
18 else: |
|
19 def printComments(fd, clist, indent): |
|
20 pass |
|
21 |
|
22 def firstCap(str): |
|
23 return str[0].upper() + str[1:] |
|
24 |
|
25 def attributeParamName(a): |
|
26 return "a" + firstCap(a.name) |
|
27 |
|
28 def attributeParamNames(a): |
|
29 l = [attributeParamName(a)] |
|
30 if a.implicit_jscontext: |
|
31 l.insert(0, "cx") |
|
32 return ", ".join(l) |
|
33 |
|
34 def attributeNativeName(a, getter): |
|
35 binaryname = a.binaryname is not None and a.binaryname or firstCap(a.name) |
|
36 return "%s%s" % (getter and 'Get' or 'Set', binaryname) |
|
37 |
|
38 def attributeReturnType(a, macro): |
|
39 """macro should be NS_IMETHOD or NS_IMETHODIMP""" |
|
40 if (a.nostdcall): |
|
41 return macro == "NS_IMETHOD" and "virtual nsresult" or "nsresult" |
|
42 else: |
|
43 return macro |
|
44 |
|
45 def attributeParamlist(a, getter): |
|
46 l = ["%s%s" % (a.realtype.nativeType(getter and 'out' or 'in'), |
|
47 attributeParamName(a))] |
|
48 if a.implicit_jscontext: |
|
49 l.insert(0, "JSContext* cx") |
|
50 |
|
51 return ", ".join(l) |
|
52 |
|
53 def attributeAsNative(a, getter): |
|
54 deprecated = a.deprecated and "NS_DEPRECATED " or "" |
|
55 params = {'deprecated': deprecated, |
|
56 'returntype': attributeReturnType(a, 'NS_IMETHOD'), |
|
57 'binaryname': attributeNativeName(a, getter), |
|
58 'paramlist': attributeParamlist(a, getter)} |
|
59 return "%(deprecated)s%(returntype)s %(binaryname)s(%(paramlist)s)" % params |
|
60 |
|
61 def methodNativeName(m): |
|
62 return m.binaryname is not None and m.binaryname or firstCap(m.name) |
|
63 |
|
64 def methodReturnType(m, macro): |
|
65 """macro should be NS_IMETHOD or NS_IMETHODIMP""" |
|
66 if m.nostdcall and m.notxpcom: |
|
67 return "%s%s" % (macro == "NS_IMETHOD" and "virtual " or "", |
|
68 m.realtype.nativeType('in').strip()) |
|
69 elif m.nostdcall: |
|
70 return "%snsresult" % (macro == "NS_IMETHOD" and "virtual " or "") |
|
71 elif m.notxpcom: |
|
72 return "%s_(%s)" % (macro, m.realtype.nativeType('in').strip()) |
|
73 else: |
|
74 return macro |
|
75 |
|
76 def methodAsNative(m): |
|
77 return "%s %s(%s)" % (methodReturnType(m, 'NS_IMETHOD'), |
|
78 methodNativeName(m), |
|
79 paramlistAsNative(m)) |
|
80 |
|
81 def paramlistAsNative(m, empty='void'): |
|
82 l = [paramAsNative(p) for p in m.params] |
|
83 |
|
84 if m.implicit_jscontext: |
|
85 l.append("JSContext* cx") |
|
86 |
|
87 if m.optional_argc: |
|
88 l.append('uint8_t _argc') |
|
89 |
|
90 if not m.notxpcom and m.realtype.name != 'void': |
|
91 l.append(paramAsNative(xpidl.Param(paramtype='out', |
|
92 type=None, |
|
93 name='_retval', |
|
94 attlist=[], |
|
95 location=None, |
|
96 realtype=m.realtype))) |
|
97 |
|
98 if len(l) == 0: |
|
99 return empty |
|
100 |
|
101 return ", ".join(l) |
|
102 |
|
103 def paramAsNative(p): |
|
104 return "%s%s" % (p.nativeType(), |
|
105 p.name) |
|
106 |
|
107 def paramlistNames(m): |
|
108 names = [p.name for p in m.params] |
|
109 |
|
110 if m.implicit_jscontext: |
|
111 names.append('cx') |
|
112 |
|
113 if m.optional_argc: |
|
114 names.append('_argc') |
|
115 |
|
116 if not m.notxpcom and m.realtype.name != 'void': |
|
117 names.append('_retval') |
|
118 |
|
119 if len(names) == 0: |
|
120 return '' |
|
121 return ', '.join(names) |
|
122 |
|
123 header = """/* |
|
124 * DO NOT EDIT. THIS FILE IS GENERATED FROM %(filename)s |
|
125 */ |
|
126 |
|
127 #ifndef __gen_%(basename)s_h__ |
|
128 #define __gen_%(basename)s_h__ |
|
129 """ |
|
130 |
|
131 include = """ |
|
132 #ifndef __gen_%(basename)s_h__ |
|
133 #include "%(basename)s.h" |
|
134 #endif |
|
135 """ |
|
136 |
|
137 jsvalue_include = """ |
|
138 #include "js/Value.h" |
|
139 """ |
|
140 |
|
141 infallible_includes = """ |
|
142 #include "mozilla/Assertions.h" |
|
143 #include "mozilla/DebugOnly.h" |
|
144 """ |
|
145 |
|
146 header_end = """/* For IDL files that don't want to include root IDL files. */ |
|
147 #ifndef NS_NO_VTABLE |
|
148 #define NS_NO_VTABLE |
|
149 #endif |
|
150 """ |
|
151 |
|
152 footer = """ |
|
153 #endif /* __gen_%(basename)s_h__ */ |
|
154 """ |
|
155 |
|
156 forward_decl = """class %(name)s; /* forward declaration */ |
|
157 |
|
158 """ |
|
159 |
|
160 def idl_basename(f): |
|
161 """returns the base name of a file with the last extension stripped""" |
|
162 return os.path.basename(f).rpartition('.')[0] |
|
163 |
|
164 def print_header(idl, fd, filename): |
|
165 fd.write(header % {'filename': filename, |
|
166 'basename': idl_basename(filename)}) |
|
167 |
|
168 foundinc = False |
|
169 for inc in idl.includes(): |
|
170 if not foundinc: |
|
171 foundinc = True |
|
172 fd.write('\n') |
|
173 fd.write(include % {'basename': idl_basename(inc.filename)}) |
|
174 |
|
175 if idl.needsJSTypes(): |
|
176 fd.write(jsvalue_include) |
|
177 |
|
178 # Include some extra files if any attributes are infallible. |
|
179 for iface in [p for p in idl.productions if p.kind == 'interface']: |
|
180 for attr in [m for m in iface.members if isinstance(m, xpidl.Attribute)]: |
|
181 if attr.infallible: |
|
182 fd.write(infallible_includes) |
|
183 break |
|
184 |
|
185 fd.write('\n') |
|
186 fd.write(header_end) |
|
187 |
|
188 for p in idl.productions: |
|
189 if p.kind == 'include': continue |
|
190 if p.kind == 'cdata': |
|
191 fd.write(p.data) |
|
192 continue |
|
193 |
|
194 if p.kind == 'forward': |
|
195 fd.write(forward_decl % {'name': p.name}) |
|
196 continue |
|
197 if p.kind == 'interface': |
|
198 write_interface(p, fd) |
|
199 continue |
|
200 if p.kind == 'typedef': |
|
201 printComments(fd, p.doccomments, '') |
|
202 fd.write("typedef %s %s;\n\n" % (p.realtype.nativeType('in'), |
|
203 p.name)) |
|
204 |
|
205 fd.write(footer % {'basename': idl_basename(filename)}) |
|
206 |
|
207 iface_header = r""" |
|
208 /* starting interface: %(name)s */ |
|
209 #define %(defname)s_IID_STR "%(iid)s" |
|
210 |
|
211 #define %(defname)s_IID \ |
|
212 {0x%(m0)s, 0x%(m1)s, 0x%(m2)s, \ |
|
213 { %(m3joined)s }} |
|
214 |
|
215 """ |
|
216 |
|
217 uuid_decoder = re.compile(r"""(?P<m0>[a-f0-9]{8})- |
|
218 (?P<m1>[a-f0-9]{4})- |
|
219 (?P<m2>[a-f0-9]{4})- |
|
220 (?P<m3>[a-f0-9]{4})- |
|
221 (?P<m4>[a-f0-9]{12})$""", re.X) |
|
222 |
|
223 iface_prolog = """ { |
|
224 public: |
|
225 |
|
226 NS_DECLARE_STATIC_IID_ACCESSOR(%(defname)s_IID) |
|
227 |
|
228 """ |
|
229 |
|
230 iface_epilog = """}; |
|
231 |
|
232 NS_DEFINE_STATIC_IID_ACCESSOR(%(name)s, %(defname)s_IID) |
|
233 |
|
234 /* Use this macro when declaring classes that implement this interface. */ |
|
235 #define NS_DECL_%(macroname)s """ |
|
236 |
|
237 |
|
238 iface_forward = """ |
|
239 |
|
240 /* Use this macro to declare functions that forward the behavior of this interface to another object. */ |
|
241 #define NS_FORWARD_%(macroname)s(_to) """ |
|
242 |
|
243 iface_forward_safe = """ |
|
244 |
|
245 /* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */ |
|
246 #define NS_FORWARD_SAFE_%(macroname)s(_to) """ |
|
247 |
|
248 iface_template_prolog = """ |
|
249 |
|
250 #if 0 |
|
251 /* Use the code below as a template for the implementation class for this interface. */ |
|
252 |
|
253 /* Header file */ |
|
254 class %(implclass)s : public %(name)s |
|
255 { |
|
256 public: |
|
257 NS_DECL_ISUPPORTS |
|
258 NS_DECL_%(macroname)s |
|
259 |
|
260 %(implclass)s(); |
|
261 |
|
262 private: |
|
263 ~%(implclass)s(); |
|
264 |
|
265 protected: |
|
266 /* additional members */ |
|
267 }; |
|
268 |
|
269 /* Implementation file */ |
|
270 NS_IMPL_ISUPPORTS(%(implclass)s, %(name)s) |
|
271 |
|
272 %(implclass)s::%(implclass)s() |
|
273 { |
|
274 /* member initializers and constructor code */ |
|
275 } |
|
276 |
|
277 %(implclass)s::~%(implclass)s() |
|
278 { |
|
279 /* destructor code */ |
|
280 } |
|
281 |
|
282 """ |
|
283 |
|
284 example_tmpl = """%(returntype)s %(implclass)s::%(nativeName)s(%(paramList)s) |
|
285 { |
|
286 return NS_ERROR_NOT_IMPLEMENTED; |
|
287 } |
|
288 """ |
|
289 |
|
290 iface_template_epilog = """/* End of implementation class template. */ |
|
291 #endif |
|
292 |
|
293 """ |
|
294 |
|
295 attr_infallible_tmpl = """\ |
|
296 inline %(realtype)s%(nativename)s(%(args)s) |
|
297 { |
|
298 %(realtype)sresult; |
|
299 mozilla::DebugOnly<nsresult> rv = %(nativename)s(%(argnames)s&result); |
|
300 MOZ_ASSERT(NS_SUCCEEDED(rv)); |
|
301 return result; |
|
302 } |
|
303 """ |
|
304 |
|
305 def write_interface(iface, fd): |
|
306 if iface.namemap is None: |
|
307 raise Exception("Interface was not resolved.") |
|
308 |
|
309 def write_const_decls(g): |
|
310 fd.write(" enum {\n") |
|
311 enums = [] |
|
312 for c in g: |
|
313 printComments(fd, c.doccomments, ' ') |
|
314 basetype = c.basetype |
|
315 value = c.getValue() |
|
316 enums.append(" %(name)s = %(value)s%(signed)s" % { |
|
317 'name': c.name, |
|
318 'value': value, |
|
319 'signed': (not basetype.signed) and 'U' or ''}) |
|
320 fd.write(",\n".join(enums)) |
|
321 fd.write("\n };\n\n") |
|
322 |
|
323 def write_method_decl(m): |
|
324 printComments(fd, m.doccomments, ' ') |
|
325 |
|
326 fd.write(" /* %s */\n" % m.toIDL()) |
|
327 fd.write(" %s = 0;\n\n" % methodAsNative(m)) |
|
328 |
|
329 def write_attr_decl(a): |
|
330 printComments(fd, a.doccomments, ' ') |
|
331 |
|
332 fd.write(" /* %s */\n" % a.toIDL()); |
|
333 |
|
334 fd.write(" %s = 0;\n" % attributeAsNative(a, True)) |
|
335 if a.infallible: |
|
336 fd.write(attr_infallible_tmpl % |
|
337 {'realtype': a.realtype.nativeType('in'), |
|
338 'nativename': attributeNativeName(a, getter=True), |
|
339 'args': '' if not a.implicit_jscontext else 'JSContext* cx', |
|
340 'argnames': '' if not a.implicit_jscontext else 'cx, '}) |
|
341 |
|
342 if not a.readonly: |
|
343 fd.write(" %s = 0;\n" % attributeAsNative(a, False)) |
|
344 fd.write("\n") |
|
345 |
|
346 defname = iface.name.upper() |
|
347 if iface.name[0:2] == 'ns': |
|
348 defname = 'NS_' + defname[2:] |
|
349 |
|
350 names = uuid_decoder.match(iface.attributes.uuid).groupdict() |
|
351 m3str = names['m3'] + names['m4'] |
|
352 names['m3joined'] = ", ".join(["0x%s" % m3str[i:i+2] for i in xrange(0, 16, 2)]) |
|
353 |
|
354 if iface.name[2] == 'I': |
|
355 implclass = iface.name[:2] + iface.name[3:] |
|
356 else: |
|
357 implclass = '_MYCLASS_' |
|
358 |
|
359 names.update({'defname': defname, |
|
360 'macroname': iface.name.upper(), |
|
361 'name': iface.name, |
|
362 'iid': iface.attributes.uuid, |
|
363 'implclass': implclass}) |
|
364 |
|
365 fd.write(iface_header % names) |
|
366 |
|
367 printComments(fd, iface.doccomments, '') |
|
368 |
|
369 fd.write("class ") |
|
370 foundcdata = False |
|
371 for m in iface.members: |
|
372 if isinstance(m, xpidl.CDATA): |
|
373 foundcdata = True |
|
374 |
|
375 if not foundcdata: |
|
376 fd.write("NS_NO_VTABLE ") |
|
377 |
|
378 if iface.attributes.deprecated: |
|
379 fd.write("MOZ_DEPRECATED ") |
|
380 fd.write(iface.name) |
|
381 if iface.base: |
|
382 fd.write(" : public %s" % iface.base) |
|
383 fd.write(iface_prolog % names) |
|
384 |
|
385 for key, group in itertools.groupby(iface.members, key=type): |
|
386 if key == xpidl.ConstMember: |
|
387 write_const_decls(group) # iterator of all the consts |
|
388 else: |
|
389 for member in group: |
|
390 if key == xpidl.Attribute: |
|
391 write_attr_decl(member) |
|
392 elif key == xpidl.Method: |
|
393 write_method_decl(member) |
|
394 elif key == xpidl.CDATA: |
|
395 fd.write(" %s" % member.data) |
|
396 else: |
|
397 raise Exception("Unexpected interface member: %s" % member) |
|
398 |
|
399 fd.write(iface_epilog % names) |
|
400 |
|
401 for member in iface.members: |
|
402 if isinstance(member, xpidl.Attribute): |
|
403 fd.write("\\\n %s; " % attributeAsNative(member, True)) |
|
404 if not member.readonly: |
|
405 fd.write("\\\n %s; " % attributeAsNative(member, False)) |
|
406 elif isinstance(member, xpidl.Method): |
|
407 fd.write("\\\n %s; " % methodAsNative(member)) |
|
408 if len(iface.members) == 0: |
|
409 fd.write('\\\n /* no methods! */') |
|
410 elif not member.kind in ('attribute', 'method'): |
|
411 fd.write('\\') |
|
412 |
|
413 fd.write(iface_forward % names) |
|
414 |
|
415 def emitTemplate(tmpl, tmpl_notxpcom=None): |
|
416 if tmpl_notxpcom == None: |
|
417 tmpl_notxpcom = tmpl |
|
418 for member in iface.members: |
|
419 if isinstance(member, xpidl.Attribute): |
|
420 fd.write(tmpl % {'asNative': attributeAsNative(member, True), |
|
421 'nativeName': attributeNativeName(member, True), |
|
422 'paramList': attributeParamNames(member)}) |
|
423 if not member.readonly: |
|
424 fd.write(tmpl % {'asNative': attributeAsNative(member, False), |
|
425 'nativeName': attributeNativeName(member, False), |
|
426 'paramList': attributeParamNames(member)}) |
|
427 elif isinstance(member, xpidl.Method): |
|
428 if member.notxpcom: |
|
429 fd.write(tmpl_notxpcom % {'asNative': methodAsNative(member), |
|
430 'nativeName': methodNativeName(member), |
|
431 'paramList': paramlistNames(member)}) |
|
432 else: |
|
433 fd.write(tmpl % {'asNative': methodAsNative(member), |
|
434 'nativeName': methodNativeName(member), |
|
435 'paramList': paramlistNames(member)}) |
|
436 if len(iface.members) == 0: |
|
437 fd.write('\\\n /* no methods! */') |
|
438 elif not member.kind in ('attribute', 'method'): |
|
439 fd.write('\\') |
|
440 |
|
441 emitTemplate("\\\n %(asNative)s { return _to %(nativeName)s(%(paramList)s); } ") |
|
442 |
|
443 fd.write(iface_forward_safe % names) |
|
444 |
|
445 # Don't try to safely forward notxpcom functions, because we have no |
|
446 # sensible default error return. Instead, the caller will have to |
|
447 # implement them. |
|
448 emitTemplate("\\\n %(asNative)s { return !_to ? NS_ERROR_NULL_POINTER : _to->%(nativeName)s(%(paramList)s); } ", |
|
449 "\\\n %(asNative)s; ") |
|
450 |
|
451 fd.write(iface_template_prolog % names) |
|
452 |
|
453 for member in iface.members: |
|
454 if isinstance(member, xpidl.ConstMember) or isinstance(member, xpidl.CDATA): continue |
|
455 fd.write("/* %s */\n" % member.toIDL()) |
|
456 if isinstance(member, xpidl.Attribute): |
|
457 fd.write(example_tmpl % {'implclass': implclass, |
|
458 'returntype': attributeReturnType(member, 'NS_IMETHODIMP'), |
|
459 'nativeName': attributeNativeName(member, True), |
|
460 'paramList': attributeParamlist(member, True)}) |
|
461 if not member.readonly: |
|
462 fd.write(example_tmpl % {'implclass': implclass, |
|
463 'returntype': attributeReturnType(member, 'NS_IMETHODIMP'), |
|
464 'nativeName': attributeNativeName(member, False), |
|
465 'paramList': attributeParamlist(member, False)}) |
|
466 elif isinstance(member, xpidl.Method): |
|
467 fd.write(example_tmpl % {'implclass': implclass, |
|
468 'returntype': methodReturnType(member, 'NS_IMETHODIMP'), |
|
469 'nativeName': methodNativeName(member), |
|
470 'paramList': paramlistAsNative(member, empty='')}) |
|
471 fd.write('\n') |
|
472 |
|
473 fd.write(iface_template_epilog) |
|
474 |
|
475 if __name__ == '__main__': |
|
476 from optparse import OptionParser |
|
477 o = OptionParser() |
|
478 o.add_option('-I', action='append', dest='incdirs', default=['.'], |
|
479 help="Directory to search for imported files") |
|
480 o.add_option('--cachedir', dest='cachedir', default=None, |
|
481 help="Directory in which to cache lex/parse tables.") |
|
482 o.add_option('-o', dest='outfile', default=None, |
|
483 help="Output file (default is stdout)") |
|
484 o.add_option('-d', dest='depfile', default=None, |
|
485 help="Generate a make dependency file") |
|
486 o.add_option('--regen', action='store_true', dest='regen', default=False, |
|
487 help="Regenerate IDL Parser cache") |
|
488 options, args = o.parse_args() |
|
489 file = args[0] if args else None |
|
490 |
|
491 if options.cachedir is not None: |
|
492 if not os.path.isdir(options.cachedir): |
|
493 os.mkdir(options.cachedir) |
|
494 sys.path.append(options.cachedir) |
|
495 |
|
496 # The only thing special about a regen is that there are no input files. |
|
497 if options.regen: |
|
498 if options.cachedir is None: |
|
499 print >>sys.stderr, "--regen useless without --cachedir" |
|
500 # Delete the lex/yacc files. Ply is too stupid to regenerate them |
|
501 # properly |
|
502 for fileglobs in [os.path.join(options.cachedir, f) for f in ["xpidllex.py*", "xpidlyacc.py*"]]: |
|
503 for filename in glob.glob(fileglobs): |
|
504 os.remove(filename) |
|
505 |
|
506 # Instantiate the parser. |
|
507 p = xpidl.IDLParser(outputdir=options.cachedir) |
|
508 |
|
509 if options.regen: |
|
510 sys.exit(0) |
|
511 |
|
512 if options.depfile is not None and options.outfile is None: |
|
513 print >>sys.stderr, "-d requires -o" |
|
514 sys.exit(1) |
|
515 |
|
516 if options.outfile is not None: |
|
517 outfd = open(options.outfile, 'w') |
|
518 closeoutfd = True |
|
519 else: |
|
520 outfd = sys.stdout |
|
521 closeoutfd = False |
|
522 |
|
523 idl = p.parse(open(file).read(), filename=file) |
|
524 idl.resolve(options.incdirs, p) |
|
525 print_header(idl, outfd, file) |
|
526 |
|
527 if closeoutfd: |
|
528 outfd.close() |
|
529 |
|
530 if options.depfile is not None: |
|
531 dirname = os.path.dirname(options.depfile) |
|
532 if dirname: |
|
533 try: |
|
534 os.makedirs(dirname) |
|
535 except: |
|
536 pass |
|
537 depfd = open(options.depfile, 'w') |
|
538 deps = [dep.replace('\\', '/') for dep in idl.deps] |
|
539 |
|
540 print >>depfd, "%s: %s" % (options.outfile, " ".join(deps)) |
|
541 for dep in deps: |
|
542 print >>depfd, "%s:" % dep |