#!/usr/bin/python2.4 # Copyright 2009, Google Inc. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following disclaimer # in the documentation and/or other materials provided with the # distribution. # * Neither the name of Google Inc. nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. """O3D interface class generator. This module generates the O3D interface classes. """ import os import syntax_tree import cpp_utils import naming class CircularDefinition(Exception): """Raised when a circular type definition is found.""" def __init__(self, type_defn): Exception.__init__(self) self.type = type_defn class BadForwardDeclaration(Exception): """Raised when an impossible forward declaration is required.""" def ForwardDecl(section, type_defn): """Emits the forward declaration of a type, if possible. Inner types (declared inside a class) cannot be forward-declared. Only classes can be forward-declared. Args: section: the section to emit to. type_defn: the Definition for the type to forward-declare. Raises: BadForwardDeclaration: an inner type or a non-class was passed as an argument. """ # inner types cannot be forward-declared if type_defn.parent.defn_type != 'Namespace': raise BadForwardDeclaration stack = type_defn.GetParentScopeStack() if type_defn.defn_type == 'Class': for scope in stack: if scope.name: section.PushNamespace(scope.name) section.EmitCode('class %s;' % type_defn.name) for scope in stack[::-1]: if scope.name: section.PopNamespace() else: raise BadForwardDeclaration class O3DInterfaceGenerator(object): """Header generator class. This class takes care of the details of generating a C++ header file containing all the definitions from a syntax tree. It contains a list of functions named after each of the Definition classes in syntax_tree, with a common signature. The appropriate function will be called for each definition, to generate the code for that definition. """ class GenerationContext(object): def __init__(self, header_scope, cpp_scope, header_section, cpp_section): self.header_scope = header_scope self.header_section = header_section self.cpp_scope = cpp_scope self.cpp_section = cpp_section self.needed_decl = set() self.needed_defn = set() self.emitted_defn = set() def Fork(self, header_scope, cpp_scope, header_section, cpp_section): new_context = type(self)(header_scope, cpp_scope, header_section, cpp_section) new_context.needed_decl = self.needed_decl new_context.needed_defn = self.needed_defn new_context.emitted_defn = self.emitted_defn return new_context def CheckType(self, need_defn, type_defn): """Checks for the definition or declaration of a type. This function helps keeping track of which types are needed to be defined or declared in the C++ file before other definitions can happen. If the definition is needed (and is not in this C++ header file), the proper include will be generated. If the type only needs to be forward-declared, the forward declaration will be output (if the type is not otherwise defined). Args: need_defn: a boolean, True if the C++ definition of the type is needed, False if only the declaration is needed. type_defn: the Definition of the type to check. """ while type_defn.defn_type == 'Array': # arrays are implicitly defined with their data type type_defn = type_defn.data_type if need_defn: if type_defn not in self.emitted_defn: self.needed_defn.add(type_defn) else: if type_defn in self.emitted_defn: return if type_defn.parent and type_defn.parent.defn_type != 'Namespace': # inner type: need the definition of the parent. self.CheckType(True, type_defn.parent) else: # only forward-declare classes. # typedefs could be forward-declared by emitting the definition again, # but this necessitates the source type to be forward-declared before. # TODO: see if it is possible to find a proper ordering that let # us forward-declare typedefs instead of needing to include the # definition. if type_defn.defn_type == 'Class': self.needed_decl.add(type_defn) else: self.needed_defn.add(type_defn) def __init__(self, output_dir, namespace): self._output_dir = output_dir self._void_type = namespace.LookupTypeRecursive('void') def GetHeaderFile(self, idl_file): return idl_file.source.split('.')[0] + '.h' def GetCppFile(self, idl_file): return idl_file.source.split('.')[0] + '.cc' def GetInterfaceInclude(self, type_defn): if self.NeedsGlue(type_defn): return self.GetHeaderFile(type_defn.source.file) else: return type_defn.GetDefinitionInclude() def GetImplementationInclude(self, type_defn): return type_defn.GetDefinitionInclude() def IsVoid(self, type_defn): return type_defn.GetFinalType() == self._void_type def NeedsGlue(self, obj): return obj.LookupBindingModel() == 'o3d' or 'glue_iface' in obj.attributes def GetSectionFromAttributes(self, parent_section, defn): """Gets the code section appropriate for a given definition. Classes have 3 definition sections: private, protected and public. This function will pick one of the sections, based on the attributes of the definition, if its parent is a class. For other scopes (namespaces) it will return the parent scope main section. Args: parent_section: the main section for the parent scope. defn: the definition. Returns: the appropriate section. """ if defn.parent and defn.parent.defn_type == 'Class': if 'private' in defn.attributes: return parent_section.GetSection('private:') or parent_section elif 'protected' in defn.attributes: return parent_section.GetSection('protected:') or parent_section else: return parent_section.GetSection('public:') or parent_section else: return parent_section def Verbatim(self, context, obj): """Generates the code for a Verbatim definition. Verbatim definitions being written for a particular type of output file, this function will check the 'verbatim' attribute, and only emit the verbatim code if it is 'cpp_header'. Args: parent_section: the main section of the parent scope. obj: the Verbatim definition. """ try: verbatim_attr = obj.attributes['verbatim'] except KeyError: source = obj.source print ('%s:%d ignoring verbatim with no verbatim attribute' % (source.file.source, source.line)) return if verbatim_attr == 'o3d_iface_header': section = self.GetSectionFromAttributes(context.header_section, obj) section.EmitCode(obj.text) elif verbatim_attr == 'o3d_iface_cpp': context.cpp_section.EmitCode(obj.text) def Typedef(self, context, obj): """Generates the code for a Typedef definition. Args: parent_section: the main section of the parent scope. obj: the Typedef definition. Returns: a list of (boolean, Definition) pairs, of all the types that need to be declared (boolean is False) or defined (boolean is True) before this definition. """ section = self.GetSectionFromAttributes(context.header_section, obj) bm = obj.type.binding_model type_string, unused_need_defn = bm.CppTypedefString(context.header_scope, obj.type) context.CheckType(True, obj.type) section.EmitCode('typedef %s %s;' % (type_string, obj.name)) def Variable(self, context, obj): """Generates the code for a Variable definition. This function will generate the member/global variable declaration, as well as the setter/getter functions if specified in the attributes. Args: parent_section: the main section of the parent scope. obj: the Variable definition. """ bm = obj.type.binding_model type_string, need_defn = bm.CppMemberString(context.header_scope, obj.type) context.CheckType(need_defn, obj.type) need_glue = self.NeedsGlue(obj) or (obj.parent.is_type and self.NeedsGlue(obj.parent)); getter_attributes = {} if 'static' in obj.attributes: getter_attributes['static'] = obj.attributes['static'] static = 'static ' else: static = '' for attr in ['public', 'protected', 'private']: if attr in obj.attributes: getter_attributes[attr] = obj.attributes[attr] if not need_glue: if obj.parent.defn_type == 'Class': if 'field_access' in obj.attributes: member_section = context.header_section.GetSection( obj.attributes['field_access'] + ':') else: member_section = context.header_section.GetSection('private:') else: member_section = context.header_section field_name = naming.Normalize(obj.name, naming.LowerTrailing) member_section.EmitCode('%s%s %s;' % (static, type_string, field_name)) if 'getter' in obj.attributes: func = obj.MakeGetter(getter_attributes, cpp_utils.GetGetterName(obj)) if need_glue: self.FunctionGlue(context, func) impl = None else: impl = ' { return %s; }' % field_name self.FunctionDecl(context, func, impl) if 'setter' in obj.attributes: func = obj.MakeSetter(getter_attributes, cpp_utils.GetSetterName(obj)) if need_glue: self.FunctionGlue(context, func) impl = None else: impl = ' { %s = %s; }' % (field_name, obj.name) self.FunctionDecl(context, func, impl) def GetParamsDecls(self, scope, obj, context=None): param_strings = [] for p in obj.params: bm = p.type.binding_model if p.mutable: text, need_defn = bm.CppMutableParameterString(scope, p.type) else: text, need_defn = bm.CppParameterString(scope, p.type) if context: context.CheckType(need_defn, p.type) param_strings += ['%s %s' % (text, p.name)] return ', '.join(param_strings) def FunctionDecl(self, context, obj, impl_string=None): section = self.GetSectionFromAttributes(context.header_section, obj) if not impl_string: impl_string = ';' params_string = self.GetParamsDecls(context.header_scope, obj, context) prefix_strings = [] suffix_strings = [] for attrib in ['static', 'virtual', 'inline']: if attrib in obj.attributes: prefix_strings.append(attrib) if prefix_strings: prefix_strings.append('') if 'const' in obj.attributes: suffix_strings.append('const') if 'pure' in obj.attributes: suffix_strings.append('= 0') if suffix_strings: suffix_strings.insert(0, '') prefix = ' '.join(prefix_strings) suffix = ' '.join(suffix_strings) if obj.type: bm = obj.type.binding_model return_type, need_defn = bm.CppReturnValueString(context.header_scope, obj.type) context.CheckType(need_defn, obj.type) section.EmitCode('%s%s %s(%s)%s%s' % (prefix, return_type, obj.name, params_string, suffix, impl_string)) else: section.EmitCode('%s%s(%s)%s%s' % (prefix, obj.name, params_string, suffix, impl_string)) def FunctionGlue(self, context, obj): if not obj.type: # TODO autogen a factory return if 'pure' in obj.attributes: return if obj.parent.is_type: func_name = '%s::%s' % (obj.parent.name, obj.name) if 'static' in obj.attributes: call_prefix = 'impl::' + func_name else: # this_call if self.NeedsGlue(obj.parent): call_prefix = 'GetImpl()->' else: call_prefix = '' else: call_prefix = '' func_name = obj.name params_string = self.GetParamsDecls(context.cpp_scope, obj) param_exprs = [] for p in obj.params: if self.NeedsGlue(p.type): param_exprs.append('%s->GetImpl()' % p.name) else: param_exprs.append(p.name) if not self.IsVoid(obj.type): return_prefix = 'return ' if self.NeedsGlue(obj.type): return_suffix = '->GetIface()' else: return_suffix = '' else: return_prefix = '' return_suffix = '' bm = obj.type.binding_model return_type, unused = bm.CppReturnValueString(context.header_scope, obj.type) if 'const' in obj.attributes: func_suffix = ' const' else: func_suffix = '' section = context.cpp_section section.EmitCode('%s %s(%s)%s {' % (return_type, func_name, params_string, func_suffix)) section.EmitCode('%s%s%s(%s)%s;' % (return_prefix, call_prefix, obj.name, ', '.join(param_exprs), return_suffix)) section.EmitCode('}') def Function(self, context, obj): """Generates the code for a Function definition. Args: parent_section: the main section of the parent scope. obj: the Function definition. """ self.FunctionDecl(context, obj) if self.NeedsGlue(obj) or (obj.parent.is_type and self.NeedsGlue(obj.parent)): self.FunctionGlue(context, obj) def Class(self, context, obj): """Generates the code for a Class definition. This function will recursively generate the code for all the definitions inside the class. These definitions will be output into one of 3 sections (private, protected, public), depending on their attributes. These individual sections will only be output if they are not empty. Args: parent_section: the main section of the parent scope. obj: the Class definition. """ h_section = self.GetSectionFromAttributes(context.header_section, obj).CreateSection(obj.name) c_section = context.cpp_section need_glue = self.NeedsGlue(obj) if need_glue: h_section.PushNamespace('impl') h_section.EmitCode('class %s;' % obj.name) h_section.PopNamespace() h_section.EmitCode('') if obj.base_type: bm = obj.base_type.binding_model h_section.EmitCode('class %s : public %s {' % (obj.name, bm.CppBaseClassString(context.header_scope, obj.base_type))) context.CheckType(True, obj.base_type) else: h_section.EmitCode('class %s {' % obj.name) public_section = h_section.CreateSection('public:') protected_section = h_section.CreateSection('protected:') private_section = h_section.CreateSection('private:') new_context = context.Fork(obj, context.cpp_scope, h_section, c_section) self.DefinitionList(new_context, obj.defn_list) if need_glue: public_section.EmitCode('impl::%s *GetImpl();' % obj.name) c_section.EmitCode('impl::%s *%s::GetImpl() {' % (obj.name, obj.name)) c_section.EmitCode('return static_cast(impl_);' % obj.name) c_section.EmitCode('}') if not public_section.IsEmpty(): public_section.AddPrefix('public:') if not protected_section.IsEmpty(): protected_section.AddPrefix('protected:') if not private_section.IsEmpty(): private_section.AddPrefix('private:') h_section.EmitCode('};') def Namespace(self, context, obj): """Generates the code for a Namespace definition. This function will recursively generate the code for all the definitions inside the namespace. Args: parent_section: the main section of the parent scope. obj: the Namespace definition. """ context.header_section.PushNamespace(obj.name) context.cpp_section.PushNamespace(obj.name) new_context = context.Fork(obj, obj, context.header_section, context.cpp_section) self.DefinitionList(new_context, obj.defn_list) context.header_section.PopNamespace() context.cpp_section.PopNamespace() def Typename(self, context, obj): """Generates the code for a Typename definition. Since typenames (undefined types) cannot be expressed in C++, this function will not output code. The definition may be output with a verbatim section. Args: parent_section: the main section of the parent scope. scope: the parent scope. obj: the Typename definition. """ def Enum(self, context, obj): """Generates the code for an Enum definition. Args: parent_section: the main section of the parent scope. scope: the parent scope. obj: the Enum definition. """ section = self.GetSectionFromAttributes(context.header_section, obj) section.EmitCode('enum %s {' % obj.name) for value in obj.values: if value.value is None: section.EmitCode('%s,' % value.name) else: section.EmitCode('%s = %s,' % (value.name, value.value)) section.EmitCode('};') def DefinitionList(self, context, defn_list): """Generates the code for all the definitions in a list. Args: parent_section: the main section of the parent scope. scope: the parent scope. defn_list: the list of definitions. """ for obj in defn_list: context.emitted_defn.add(obj) # array types are implicitly defined for k in obj.array_defns: context.emitted_defn.add(obj.array_defns[k]) getattr(self, obj.defn_type)(context, obj) def Generate(self, idl_file, namespace, defn_list): """Generates the header file. Args: idl_file: the source IDL file containing the definitions, as a idl_parser.File instance. namespace: a Definition for the global namespace. defn_list: the list of top-level definitions. Returns: a cpp_utils.CppFileWriter that contains the C++ header file code. Raises: CircularDefinition: circular definitions were found in the file. """ all_defn = syntax_tree.GetObjectsRecursive(defn_list) need_glue = False for defn in all_defn: if self.NeedsGlue(defn): need_glue = True break if not need_glue: return [] header_writer = cpp_utils.CppFileWriter( '%s/%s' % (self._output_dir, self.GetHeaderFile(idl_file)), True) cpp_writer = cpp_utils.CppFileWriter( '%s/%s' % (self._output_dir, self.GetCppFile(idl_file)), True) h_decl_section = header_writer.CreateSection('decls') h_code_section = header_writer.CreateSection('defns') c_code_section = cpp_writer.CreateSection('glue') context = self.GenerationContext(namespace, namespace, h_code_section, c_code_section) self.DefinitionList(context, defn_list) context.needed_decl -= context.needed_defn if context.needed_decl: for type_defn in context.needed_decl: # TODO: sort by namespace so that we don't open and close them # more than necessary. ForwardDecl(h_decl_section, type_defn) h_decl_section.EmitCode('') for type_defn in context.needed_defn: if type_defn.source.file == idl_file: raise CircularDefinition(type_defn) h_includes = set(self.GetInterfaceInclude(type_defn) for type_defn in context.needed_defn) c_includes = set(self.GetImplementationInclude(type_defn) for type_defn in context.emitted_defn if self.NeedsGlue(type_defn)) c_includes.add(self.GetHeaderFile(idl_file)) for include_file in h_includes: if include_file is not None: header_writer.AddInclude(include_file) for include_file in c_includes: if include_file is not None: cpp_writer.AddInclude(include_file) return [header_writer, cpp_writer] def ProcessFiles(output_dir, pairs, namespace): """Generates the headers for all input files. Args: output_dir: the output directory. pairs: a list of (idl_parser.File, syntax_tree.Definition list) describing the list of top-level definitions in each source file. namespace: a syntax_tree.Namespace for the global namespace. Returns: a list of cpp_utils.CppFileWriter, one for each output file. """ output_dir = output_dir + '/iface' if not os.access(output_dir + '/', os.F_OK): os.makedirs(output_dir) generator = O3DInterfaceGenerator(output_dir, namespace) writer_list = [] for (f, defn) in pairs: writer_list += generator.Generate(f, namespace, defn) return writer_list def main(): pass if __name__ == '__main__': main()