#!/usr/bin/env python # header.py - Generate C++ header files from IDL. # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. """Print a C++ header file for the IDL files specified on the command line""" import sys import os.path import re import xpidl import itertools import glob printdoccomments = False if printdoccomments: def printComments(fd, clist, indent): for c in clist: fd.write("%s%s\n" % (indent, c)) else: def printComments(fd, clist, indent): pass def firstCap(str): return str[0].upper() + str[1:] def attributeParamName(a): return "a" + firstCap(a.name) def attributeParamNames(a): l = [attributeParamName(a)] if a.implicit_jscontext: l.insert(0, "cx") return ", ".join(l) def attributeNativeName(a, getter): binaryname = a.binaryname is not None and a.binaryname or firstCap(a.name) return "%s%s" % (getter and 'Get' or 'Set', binaryname) def attributeReturnType(a, macro): """macro should be NS_IMETHOD or NS_IMETHODIMP""" if a.nostdcall: ret = macro == "NS_IMETHOD" and "virtual nsresult" or "nsresult" else: ret = macro if a.must_use: ret = "MOZ_MUST_USE " + ret return ret def attributeParamlist(a, getter): l = ["%s%s" % (a.realtype.nativeType(getter and 'out' or 'in'), attributeParamName(a))] if a.implicit_jscontext: l.insert(0, "JSContext* cx") return ", ".join(l) def attributeAsNative(a, getter, declType = 'NS_IMETHOD'): deprecated = a.deprecated and "NS_DEPRECATED " or "" params = {'deprecated': deprecated, 'returntype': attributeReturnType(a, declType), 'binaryname': attributeNativeName(a, getter), 'paramlist': attributeParamlist(a, getter)} return "%(deprecated)s%(returntype)s %(binaryname)s(%(paramlist)s)" % params def methodNativeName(m): return m.binaryname is not None and m.binaryname or firstCap(m.name) def methodReturnType(m, macro): """macro should be NS_IMETHOD or NS_IMETHODIMP""" if m.nostdcall and m.notxpcom: ret = "%s%s" % (macro == "NS_IMETHOD" and "virtual " or "", m.realtype.nativeType('in').strip()) elif m.nostdcall: ret = "%snsresult" % (macro == "NS_IMETHOD" and "virtual " or "") elif m.notxpcom: ret = "%s_(%s)" % (macro, m.realtype.nativeType('in').strip()) else: ret = macro if m.must_use: ret = "MOZ_MUST_USE " + ret return ret def methodAsNative(m, declType = 'NS_IMETHOD'): return "%s %s(%s)" % (methodReturnType(m, declType), methodNativeName(m), paramlistAsNative(m)) def paramlistAsNative(m, empty='void'): l = [paramAsNative(p) for p in m.params] if m.implicit_jscontext: l.append("JSContext* cx") if m.optional_argc: l.append('uint8_t _argc') if not m.notxpcom and m.realtype.name != 'void': l.append(paramAsNative(xpidl.Param(paramtype='out', type=None, name='_retval', attlist=[], location=None, realtype=m.realtype))) # Set any optional out params to default to nullptr. Skip if we just added # extra non-optional args to l. if len(l) == len(m.params): paramIter = len(m.params) - 1 while (paramIter >= 0 and m.params[paramIter].optional and m.params[paramIter].paramtype == "out"): t = m.params[paramIter].type # Strings can't be optional, so this shouldn't happen, but let's make sure: if t == "AString" or t == "ACString" or t == "DOMString" or t == "AUTF8String": break l[paramIter] += " = nullptr" paramIter -= 1 if len(l) == 0: return empty return ", ".join(l) def paramAsNative(p): return "%s%s" % (p.nativeType(), p.name) def paramlistNames(m): names = [p.name for p in m.params] if m.implicit_jscontext: names.append('cx') if m.optional_argc: names.append('_argc') if not m.notxpcom and m.realtype.name != 'void': names.append('_retval') if len(names) == 0: return '' return ', '.join(names) header = """/* * DO NOT EDIT. THIS FILE IS GENERATED FROM %(filename)s */ #ifndef __gen_%(basename)s_h__ #define __gen_%(basename)s_h__ """ include = """ #ifndef __gen_%(basename)s_h__ #include "%(basename)s.h" #endif """ jsvalue_include = """ #include "js/Value.h" """ infallible_includes = """ #include "mozilla/Assertions.h" #include "mozilla/DebugOnly.h" """ header_end = """/* For IDL files that don't want to include root IDL files. */ #ifndef NS_NO_VTABLE #define NS_NO_VTABLE #endif """ footer = """ #endif /* __gen_%(basename)s_h__ */ """ forward_decl = """class %(name)s; /* forward declaration */ """ def idl_basename(f): """returns the base name of a file with the last extension stripped""" return os.path.basename(f).rpartition('.')[0] def print_header(idl, fd, filename): fd.write(header % {'filename': filename, 'basename': idl_basename(filename)}) foundinc = False for inc in idl.includes(): if not foundinc: foundinc = True fd.write('\n') fd.write(include % {'basename': idl_basename(inc.filename)}) if idl.needsJSTypes(): fd.write(jsvalue_include) # Include some extra files if any attributes are infallible. for iface in [p for p in idl.productions if p.kind == 'interface']: for attr in [m for m in iface.members if isinstance(m, xpidl.Attribute)]: if attr.infallible: fd.write(infallible_includes) break fd.write('\n') fd.write(header_end) for p in idl.productions: if p.kind == 'include': continue if p.kind == 'cdata': fd.write(p.data) continue if p.kind == 'forward': fd.write(forward_decl % {'name': p.name}) continue if p.kind == 'interface': write_interface(p, fd) continue if p.kind == 'typedef': printComments(fd, p.doccomments, '') fd.write("typedef %s %s;\n\n" % (p.realtype.nativeType('in'), p.name)) fd.write(footer % {'basename': idl_basename(filename)}) iface_header = r""" /* starting interface: %(name)s */ #define %(defname)s_IID_STR "%(iid)s" #define %(defname)s_IID \ {0x%(m0)s, 0x%(m1)s, 0x%(m2)s, \ { %(m3joined)s }} """ uuid_decoder = re.compile(r"""(?P[a-f0-9]{8})- (?P[a-f0-9]{4})- (?P[a-f0-9]{4})- (?P[a-f0-9]{4})- (?P[a-f0-9]{12})$""", re.X) iface_prolog = """ { public: NS_DECLARE_STATIC_IID_ACCESSOR(%(defname)s_IID) """ iface_epilog = """}; NS_DEFINE_STATIC_IID_ACCESSOR(%(name)s, %(defname)s_IID) /* Use this macro when declaring classes that implement this interface. */ #define NS_DECL_%(macroname)s """ iface_nonvirtual = """ /* Use this macro when declaring the members of this interface when the class doesn't implement the interface. This is useful for forwarding. */ #define NS_DECL_NON_VIRTUAL_%(macroname)s """ iface_forward = """ /* Use this macro to declare functions that forward the behavior of this interface to another object. */ #define NS_FORWARD_%(macroname)s(_to) """ iface_forward_safe = """ /* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */ #define NS_FORWARD_SAFE_%(macroname)s(_to) """ iface_template_prolog = """ #if 0 /* Use the code below as a template for the implementation class for this interface. */ /* Header file */ class %(implclass)s : public %(name)s { public: NS_DECL_ISUPPORTS NS_DECL_%(macroname)s %(implclass)s(); private: ~%(implclass)s(); protected: /* additional members */ }; /* Implementation file */ NS_IMPL_ISUPPORTS(%(implclass)s, %(name)s) %(implclass)s::%(implclass)s() { /* member initializers and constructor code */ } %(implclass)s::~%(implclass)s() { /* destructor code */ } """ example_tmpl = """%(returntype)s %(implclass)s::%(nativeName)s(%(paramList)s) { return NS_ERROR_NOT_IMPLEMENTED; } """ iface_template_epilog = """/* End of implementation class template. */ #endif """ attr_infallible_tmpl = """\ inline %(realtype)s%(nativename)s(%(args)s) { %(realtype)sresult; mozilla::DebugOnly rv = %(nativename)s(%(argnames)s&result); MOZ_ASSERT(NS_SUCCEEDED(rv)); return result; } """ def write_interface(iface, fd): if iface.namemap is None: raise Exception("Interface was not resolved.") # Confirm that no names of methods will overload in this interface names = set() def record_name(name): if name in names: raise Exception("Unexpected overloaded virtual method %s in interface %s" % (name, iface.name)) names.add(name) for m in iface.members: if type(m) == xpidl.Attribute: record_name(attributeNativeName(m, getter=True)) if not m.readonly: record_name(attributeNativeName(m, getter=False)) elif type(m) == xpidl.Method: record_name(methodNativeName(m)) def write_const_decls(g): fd.write(" enum {\n") enums = [] for c in g: printComments(fd, c.doccomments, ' ') basetype = c.basetype value = c.getValue() enums.append(" %(name)s = %(value)s%(signed)s" % { 'name': c.name, 'value': value, 'signed': (not basetype.signed) and 'U' or ''}) fd.write(",\n".join(enums)) fd.write("\n };\n\n") def write_method_decl(m): printComments(fd, m.doccomments, ' ') fd.write(" /* %s */\n" % m.toIDL()) fd.write(" %s = 0;\n\n" % methodAsNative(m)) def write_attr_decl(a): printComments(fd, a.doccomments, ' ') fd.write(" /* %s */\n" % a.toIDL()) fd.write(" %s = 0;\n" % attributeAsNative(a, True)) if a.infallible: fd.write(attr_infallible_tmpl % {'realtype': a.realtype.nativeType('in'), 'nativename': attributeNativeName(a, getter=True), 'args': '' if not a.implicit_jscontext else 'JSContext* cx', 'argnames': '' if not a.implicit_jscontext else 'cx, '}) if not a.readonly: fd.write(" %s = 0;\n" % attributeAsNative(a, False)) fd.write("\n") defname = iface.name.upper() if iface.name[0:2] == 'ns': defname = 'NS_' + defname[2:] names = uuid_decoder.match(iface.attributes.uuid).groupdict() m3str = names['m3'] + names['m4'] names['m3joined'] = ", ".join(["0x%s" % m3str[i:i+2] for i in xrange(0, 16, 2)]) if iface.name[2] == 'I': implclass = iface.name[:2] + iface.name[3:] else: implclass = '_MYCLASS_' names.update({'defname': defname, 'macroname': iface.name.upper(), 'name': iface.name, 'iid': iface.attributes.uuid, 'implclass': implclass}) fd.write(iface_header % names) printComments(fd, iface.doccomments, '') fd.write("class ") foundcdata = False for m in iface.members: if isinstance(m, xpidl.CDATA): foundcdata = True if not foundcdata: fd.write("NS_NO_VTABLE ") if iface.attributes.deprecated: fd.write("MOZ_DEPRECATED ") fd.write(iface.name) if iface.base: fd.write(" : public %s" % iface.base) fd.write(iface_prolog % names) for key, group in itertools.groupby(iface.members, key=type): if key == xpidl.ConstMember: write_const_decls(group) # iterator of all the consts else: for member in group: if key == xpidl.Attribute: write_attr_decl(member) elif key == xpidl.Method: write_method_decl(member) elif key == xpidl.CDATA: fd.write(" %s" % member.data) else: raise Exception("Unexpected interface member: %s" % member) fd.write(iface_epilog % names) def writeDeclaration(fd, iface, virtual): declType = "NS_IMETHOD" if virtual else "NS_METHOD" suffix = " override" if virtual else "" for member in iface.members: if isinstance(member, xpidl.Attribute): if member.infallible: fd.write("\\\n using %s::%s; " % (iface.name, attributeNativeName(member, True))) fd.write("\\\n %s%s; " % (attributeAsNative(member, True, declType), suffix)) if not member.readonly: fd.write("\\\n %s%s; " % (attributeAsNative(member, False, declType), suffix)) elif isinstance(member, xpidl.Method): fd.write("\\\n %s%s; " % (methodAsNative(member, declType), suffix)) if len(iface.members) == 0: fd.write('\\\n /* no methods! */') elif not member.kind in ('attribute', 'method'): fd.write('\\') writeDeclaration(fd, iface, True); fd.write(iface_nonvirtual % names) writeDeclaration(fd, iface, False); fd.write(iface_forward % names) def emitTemplate(forward_infallible, tmpl, tmpl_notxpcom=None): if tmpl_notxpcom is None: tmpl_notxpcom = tmpl for member in iface.members: if isinstance(member, xpidl.Attribute): if forward_infallible and member.infallible: fd.write("\\\n using %s::%s; " % (iface.name, attributeNativeName(member, True))) fd.write(tmpl % {'asNative': attributeAsNative(member, True), 'nativeName': attributeNativeName(member, True), 'paramList': attributeParamNames(member)}) if not member.readonly: fd.write(tmpl % {'asNative': attributeAsNative(member, False), 'nativeName': attributeNativeName(member, False), 'paramList': attributeParamNames(member)}) elif isinstance(member, xpidl.Method): if member.notxpcom: fd.write(tmpl_notxpcom % {'asNative': methodAsNative(member), 'nativeName': methodNativeName(member), 'paramList': paramlistNames(member)}) else: fd.write(tmpl % {'asNative': methodAsNative(member), 'nativeName': methodNativeName(member), 'paramList': paramlistNames(member)}) if len(iface.members) == 0: fd.write('\\\n /* no methods! */') elif not member.kind in ('attribute', 'method'): fd.write('\\') emitTemplate(True, "\\\n %(asNative)s override { return _to %(nativeName)s(%(paramList)s); } ") fd.write(iface_forward_safe % names) # Don't try to safely forward notxpcom functions, because we have no # sensible default error return. Instead, the caller will have to # implement them. emitTemplate(False, "\\\n %(asNative)s override { return !_to ? NS_ERROR_NULL_POINTER : _to->%(nativeName)s(%(paramList)s); } ", "\\\n %(asNative)s override; ") fd.write(iface_template_prolog % names) for member in iface.members: if isinstance(member, xpidl.ConstMember) or isinstance(member, xpidl.CDATA): continue fd.write("/* %s */\n" % member.toIDL()) if isinstance(member, xpidl.Attribute): fd.write(example_tmpl % {'implclass': implclass, 'returntype': attributeReturnType(member, 'NS_IMETHODIMP'), 'nativeName': attributeNativeName(member, True), 'paramList': attributeParamlist(member, True)}) if not member.readonly: fd.write(example_tmpl % {'implclass': implclass, 'returntype': attributeReturnType(member, 'NS_IMETHODIMP'), 'nativeName': attributeNativeName(member, False), 'paramList': attributeParamlist(member, False)}) elif isinstance(member, xpidl.Method): fd.write(example_tmpl % {'implclass': implclass, 'returntype': methodReturnType(member, 'NS_IMETHODIMP'), 'nativeName': methodNativeName(member), 'paramList': paramlistAsNative(member, empty='')}) fd.write('\n') fd.write(iface_template_epilog) def main(outputfile): cachedir = '.' if not os.path.isdir(cachedir): os.mkdir(cachedir) sys.path.append(cachedir) # Delete the lex/yacc files. Ply is too stupid to regenerate them # properly for fileglobs in [os.path.join(cachedir, f) for f in ["xpidllex.py*", "xpidlyacc.py*"]]: for filename in glob.glob(fileglobs): os.remove(filename) # Instantiate the parser. p = xpidl.IDLParser(outputdir=cachedir) if __name__ == '__main__': main(None)