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