[TYPEDEF] transformer.py - function on typedef causes problems with GLib
[gnome.gobject-introspection] / giscanner / transformer.py
index 7493a35..29753ad 100644 (file)
 #
 
 import os
+import sys
 
-from .ast import (Callback, Enum, Function, Namespace, Member,
+from .ast import (Bitfield, Callback, Enum, Function, Namespace, Member,
                   Parameter, Return, Struct, Field,
                   Type, Array, Alias, Interface, Class, Node, Union,
                   Varargs, Constant, type_name_from_ctype,
                   type_names, TYPE_STRING, BASIC_GIR_TYPES)
-from .config import DATADIR
+from .config import DATADIR, GIR_DIR, GIR_SUFFIX
 from .glibast import GLibBoxed
 from .girparser import GIRParser
 from .odict import odict
 from .sourcescanner import (
     SourceSymbol, ctype_name, CTYPE_POINTER,
     CTYPE_BASIC_TYPE, CTYPE_UNION, CTYPE_ARRAY, CTYPE_TYPEDEF,
-    CTYPE_VOID, CTYPE_ENUM, CTYPE_FUNCTION, CTYPE_STRUCT,
+    CTYPE_VOID, CTYPE_ENUM, CTYPE_FUNCTION, CTYPE_STRUCT, CTYPE_INVALID,
     CSYMBOL_TYPE_FUNCTION, CSYMBOL_TYPE_TYPEDEF, CSYMBOL_TYPE_STRUCT,
     CSYMBOL_TYPE_ENUM, CSYMBOL_TYPE_UNION, CSYMBOL_TYPE_OBJECT,
     CSYMBOL_TYPE_MEMBER, CSYMBOL_TYPE_ELLIPSIS, CSYMBOL_TYPE_CONST,
     TYPE_QUALIFIER_CONST)
-from .utils import strip_common_prefix, to_underscores
+from .utils import to_underscores
 
 _xdg_data_dirs = [x for x in os.environ.get('XDG_DATA_DIRS', '').split(':') \
                       + [DATADIR, '/usr/share'] if x]
@@ -46,6 +47,8 @@ _xdg_data_dirs = [x for x in os.environ.get('XDG_DATA_DIRS', '').split(':') \
 class SkipError(Exception):
     pass
 
+class VaListSkipError(SkipError):
+    pass
 
 class Names(object):
     names = property(lambda self: self._names)
@@ -63,12 +66,12 @@ class Names(object):
 
 class Transformer(object):
 
-    def __init__(self, cachestore, generator,
-                 namespace_name, namespace_version):
+    def __init__(self, cachestore, namespace_name, namespace_version):
         self._cachestore = cachestore
-        self.generator = generator
+        self.generator = None
         self._namespace = Namespace(namespace_name, namespace_version)
         self._names = Names()
+        self._pkg_config_packages = set()
         self._typedefs_ns = {}
         self._strip_prefix = ''
         self._includes = set()
@@ -80,13 +83,28 @@ class Transformer(object):
     def get_includes(self):
         return self._includes
 
+    def set_strip_suffix(self, strip_suffix):
+        self._strip_suffix = strip_suffix
+
     def set_strip_prefix(self, strip_prefix):
         self._strip_prefix = strip_prefix
 
+    def get_strip_prefix(self):
+        return self._strip_prefix
+
+    def get_pkgconfig_packages(self):
+        return self._pkg_config_packages
+
+    def set_source_ast(self, src_ast):
+        self.generator = src_ast
+
     def parse(self):
         nodes = []
         for symbol in self.generator.get_symbols():
-            node = self._traverse_one(symbol)
+            try:
+                node = self._traverse_one(symbol)
+            except SkipError:
+                continue
             self._add_node(node)
         return self._namespace
 
@@ -105,16 +123,17 @@ class Transformer(object):
     def _find_include(self, include):
         searchdirs = self._includepaths[:]
         for path in _xdg_data_dirs:
-            searchdirs.append(os.path.join(path, 'gir'))
+            searchdirs.append(os.path.join(path, GIR_SUFFIX))
+        searchdirs.append(GIR_DIR)
 
         girname = '%s-%s.gir' % (include.name, include.version)
         for d in searchdirs:
             path = os.path.join(d, girname)
             if os.path.exists(path):
                 return path
-        else:
-            raise ValueError("Couldn't find include %r (search path: %r)"\
-                             % (girname, searchdirs))
+        sys.stderr.write("Couldn't find include %r (search path: %r)\n"\
+                         % (girname, searchdirs))
+        sys.exit(1)
 
     def _parse_include(self, filename):
         parser = self._cachestore.load(filename)
@@ -127,6 +146,8 @@ class Transformer(object):
         for include in parser.get_includes():
             self.register_include(include)
 
+        for pkg in parser.get_pkgconfig_packages():
+            self._pkg_config_packages.add(pkg)
         namespace = parser.get_namespace()
         nsname = namespace.name
         for node in namespace.nodes:
@@ -163,14 +184,23 @@ class Transformer(object):
         # when --strip-prefix=g:
         #   GHashTable -> HashTable
         #   g_hash_table_new -> hash_table_new
+        stripped = False
         prefix = self._strip_prefix.lower()
-        if isfunction:
+        
+        if isfunction and '_' in name:
             prefix += '_'
         if len(name) > len(prefix) and name.lower().startswith(prefix):
             name = name[len(prefix):]
+            stripped = True
 
         while name.startswith('_'):
             name = name[1:]
+
+        if (stripped and self._strip_suffix and 
+            len(name) > len(self._strip_suffix) and
+            name.endswith(self._strip_suffix)):
+            name = name[:-1*len(self._strip_suffix)]
+            
         return name
 
     def _traverse_one(self, symbol, stype=None):
@@ -179,10 +209,7 @@ class Transformer(object):
         if stype is None:
             stype = symbol.type
         if stype == CSYMBOL_TYPE_FUNCTION:
-            try:
-                return self._create_function(symbol)
-            except SkipError:
-                return
+            return self._create_function(symbol)
         elif stype == CSYMBOL_TYPE_TYPEDEF:
             return self._create_typedef(symbol)
         elif stype == CSYMBOL_TYPE_STRUCT:
@@ -201,41 +228,79 @@ class Transformer(object):
             raise NotImplementedError(
                 'Transformer: unhandled symbol: %r' % (symbol, ))
 
+    def _enum_common_prefix(self, symbol):
+        def common_prefix(a, b):
+            commonparts = []
+            for aword, bword in zip(a.split('_'), b.split('_')):
+                if aword != bword:
+                    return '_'.join(commonparts) + '_'
+                commonparts.append(aword)
+            return min(a, b)
+
+        # Nothing less than 2 has a common prefix
+        if len(list(symbol.base_type.child_list)) < 2:
+            return None
+        prefix = None
+        for child in symbol.base_type.child_list:
+            if prefix is None:
+                prefix = child.ident
+            else:
+                prefix = common_prefix(prefix, child.ident)
+                if prefix == '':
+                    return None
+        return prefix
+
     def _create_enum(self, symbol):
+        prefix = self._enum_common_prefix(symbol)
+        if prefix:
+            prefixlen = len(prefix)
+        else:
+            prefixlen = 0
         members = []
         for child in symbol.base_type.child_list:
-            name = strip_common_prefix(symbol.ident, child.ident).lower()
-            members.append(Member(name,
+            if prefixlen > 0:
+                name = child.ident[prefixlen:]
+            else:
+                # Ok, the enum members don't have a consistent prefix
+                # among them, so let's just remove the global namespace
+                # prefix.
+                name = self.remove_prefix(child.ident)
+            members.append(Member(name.lower(),
                                   child.const_int,
                                   child.ident))
 
         enum_name = self.remove_prefix(symbol.ident)
-        enum = Enum(enum_name, symbol.ident, members)
-        self._names.type_names[symbol.ident] = (None, enum)
-        return enum
+        if symbol.base_type.is_bitfield:
+            klass = Bitfield
+        else:
+            klass = Enum
+        node = klass(enum_name, symbol.ident, members)
+        self._names.type_names[symbol.ident] = (None, node)
+        return node
 
     def _create_object(self, symbol):
         return Member(symbol.ident, symbol.base_type.name,
                       symbol.ident)
 
     def _type_is_callback(self, type):
-        if (isinstance(type, Callback) or
-            isinstance(self._typedefs_ns.get(type.name), Callback)):
+        if isinstance(type, Callback):
+            return True
+        node = self._names.names.get(type.name)
+        if node and isinstance(node[1], Callback):
             return True
         return False
 
     def _handle_closure(self, param, closure_idx, closure_param):
         if (closure_param.type.name == 'any' and
-            closure_param.name == 'user_data'):
+            closure_param.name.endswith('data')):
             param.closure_name = closure_param.name
             param.closure_index = closure_idx
             return True
         return False
 
     def _handle_destroy(self, param, destroy_idx, destroy_param):
-        if ((self._namespace.name == 'GLib' and
-             destroy_param.type.name == 'DestroyNotify') or
-            destroy_param.type.name == 'GLib.DestroyNotify'):
+        if (destroy_param.type.name == 'GLib.DestroyNotify' or
+            destroy_param.type.ctype == 'GDestroyNotify'):
             param.destroy_name = destroy_param.name
             param.destroy_index = destroy_idx
             return True
@@ -246,13 +311,17 @@ class Transformer(object):
             if not self._type_is_callback(param.type):
                 continue
 
+            # set a default scope
+            if param.scope is None:
+                param.scope = 'call'
+
             # j is the index where we look for closure/destroy to
             # group with the callback param
             j = i + 1
             if j == len(params):
-                continue # no more args -> nothing to group look
-            # at the param directly following for either a closure
-            # or a destroy; only one of these will fire
+                continue # no more args -> nothing to group
+            # look at the param directly following for either a
+            # closure or a destroy; only one of these will fire
             had_closure = self._handle_closure(param, j, params[j])
             had_destroy = self._handle_destroy(param, j, params[j])
             j += 1
@@ -281,12 +350,21 @@ class Transformer(object):
             value = 'void'
         elif source_type.type == CTYPE_BASIC_TYPE:
             value = source_type.name
+            # skip adding invalid types.
+            if not source_type.base_type:
+                return value
+            value_add = self._create_source_type(source_type.base_type)
+            if len(value_add):
+                value += ' ' + value_add
         elif source_type.type == CTYPE_TYPEDEF:
             value = source_type.name
         elif source_type.type == CTYPE_ARRAY:
             return self._create_source_type(source_type.base_type)
         elif source_type.type == CTYPE_POINTER:
             value = self._create_source_type(source_type.base_type) + '*'
+        elif source_type.type == CTYPE_INVALID:
+            #this happens if const is after the type..
+            value = ''
         else:
             value = 'any'
         return value
@@ -302,7 +380,21 @@ class Transformer(object):
         source_type = symbol.base_type
         if (source_type.type == CTYPE_POINTER and
             symbol.base_type.base_type.type == CTYPE_FUNCTION):
-            node = self._create_callback(symbol)
+            try:
+                node = self._create_callback(symbol)
+            except VaListSkipError:
+                #this handles va_list members, and converts them
+                #to unwritable, unreadable void*
+                ftype = Type("any", "void*")
+                ftype = self.resolve_param_type(ftype)
+                node = Field(symbol.ident, ftype, ftype.name,
+                         readable=False, writable=False, bits=symbol.const_int)
+
+
+        elif source_type.type == CTYPE_STRUCT and source_type.name is None:
+            node = self._create_struct(symbol, anonymous=True)
+        elif source_type.type == CTYPE_UNION and source_type.name is None:
+            node = self._create_union(symbol, anonymous=True)
         else:
             # Special handling for fields; we don't have annotations on them
             # to apply later, yet.
@@ -334,6 +426,8 @@ class Transformer(object):
         if (ctype == CTYPE_POINTER and
             symbol.base_type.base_type.type == CTYPE_FUNCTION):
             node = self._create_typedef_callback(symbol)
+#        if (ctype ==  CTYPE_FUNCTION):
+#            node = self._create_typedef_callback(symbol)
         elif (ctype == CTYPE_POINTER and
             symbol.base_type.base_type.type == CTYPE_STRUCT):
             node = self._create_typedef_struct(symbol, disguised=True)
@@ -350,6 +444,13 @@ class Transformer(object):
             name = self.remove_prefix(symbol.ident)
             if symbol.base_type.name:
                 target = self.remove_prefix(symbol.base_type.name)
+            elif (ctype == CTYPE_POINTER and
+                symbol.base_type.base_type.name):
+                target = self.remove_prefix(
+                    symbol.base_type.base_type.name) + '*'
+            elif (ctype == CTYPE_POINTER and
+                symbol.base_type.base_type.type == CTYPE_VOID):
+                target = 'any'
             else:
                 target = 'none'
             if name in type_names:
@@ -404,8 +505,8 @@ class Transformer(object):
 
     def _create_type(self, source_type, is_param, is_retval):
         ctype = self._create_source_type(source_type)
-        if ctype == 'va_list':
-            raise SkipError()
+        if ctype.startswith('va_list'):
+            raise VaListSkipError()
         # FIXME: FILE* should not be skipped, it should be handled
         #        properly instead
         elif ctype == 'FILE*':
@@ -422,9 +523,9 @@ class Transformer(object):
         rettype.derefed_canonical = self._canonicalize_ctype(derefed_ctype)
 
         canontype = type_name_from_ctype(ctype)
-        if ((canontype == TYPE_STRING or
-             source_type.type == CTYPE_POINTER) and
-            source_type.base_type.type_qualifier & TYPE_QUALIFIER_CONST):
+        # Is it a const char * or a const gpointer?
+        if ((canontype == TYPE_STRING or source_type.type == CTYPE_POINTER) and
+            (source_type.base_type.type_qualifier & TYPE_QUALIFIER_CONST)):
             rettype.is_const = True
         return rettype
 
@@ -445,13 +546,24 @@ class Transformer(object):
         return return_
 
     def _create_const(self, symbol):
+        # Don't create constants for non-public things
+        # http://bugzilla.gnome.org/show_bug.cgi?id=572790
+        if (symbol.source_filename is None or
+            not symbol.source_filename.endswith('.h')):
+            return None
         name = self.remove_prefix(symbol.ident)
-        if symbol.const_string is None:
+        if symbol.const_string is not None:
+            type_name = 'utf8'
+            value = symbol.const_string
+        elif symbol.const_int is not None:
             type_name = 'int'
             value = symbol.const_int
+        elif symbol.const_double is not None:
+            type_name = 'double'
+            value = symbol.const_double
         else:
-            type_name = 'utf8'
-            value = symbol.const_string
+            raise AssertionError()
+
         const = Constant(name, type_name, value)
         return const
 
@@ -474,54 +586,59 @@ class Transformer(object):
         self._typedefs_ns[callback.name] = callback
         return callback
 
-    def _create_struct(self, symbol):
-        struct = self._typedefs_ns.get(symbol.ident, None)
-        if struct is None:
+    def _create_compound(self, klass, symbol, anonymous):
+        if symbol.ident is None:
+            # the compound is an anonymous member of another union or a struct
+            assert anonymous
+            compound = klass(None, None)
+        else:
+            compound = self._typedefs_ns.get(symbol.ident, None)
+
+        if compound is None:
             # This is a bit of a hack; really we should try
             # to resolve through the typedefs to find the real
             # name
             if symbol.ident.startswith('_'):
                 name = symbol.ident[1:]
+                compound = self._typedefs_ns.get(name, None)
             else:
                 name = symbol.ident
-            name = self.remove_prefix(name)
-            struct = Struct(name, symbol.ident)
+            if compound is None:
+                name = self.remove_prefix(name)
+                compound = klass(name, symbol.ident)
 
         for child in symbol.base_type.child_list:
             field = self._traverse_one(child)
             if field:
-                struct.fields.append(field)
+                compound.fields.append(field)
 
-        return struct
+        return compound
 
-    def _create_union(self, symbol):
-        union = self._typedefs_ns.get(symbol.ident, None)
-        if union is None:
-            # This is a bit of a hack; really we should try
-            # to resolve through the typedefs to find the real
-            # name
-            if symbol.ident.startswith('_'):
-                name = symbol.ident[1:]
-            else:
-                name = symbol.ident
-            name = self.remove_prefix(name)
-            union = Union(name, symbol.ident)
+    def _create_struct(self, symbol, anonymous=False):
+        return self._create_compound(Struct, symbol, anonymous)
 
-        for child in symbol.base_type.child_list:
-            field = self._traverse_one(child)
-            if field:
-                union.fields.append(field)
-
-        return union
+    def _create_union(self, symbol, anonymous=False):
+        return self._create_compound(Union, symbol, anonymous)
 
     def _create_callback(self, symbol):
-        parameters = self._create_parameters(symbol.base_type.base_type)
+        parameters = list(self._create_parameters(symbol.base_type.base_type))
         retval = self._create_return(symbol.base_type.base_type.base_type)
+        ret_type = symbol.base_type.base_type.base_type
+        if not ret_type:
+            ret_type = symbol.base_type.base_type
+        retval = self._create_return(ret_type)
+
+        # Mark the 'user_data' arguments
+        for i, param in enumerate(parameters):
+            if (param.type.name == 'any' and
+                param.name == 'user_data'):
+                param.closure_index = i
+
         if symbol.ident.find('_') > 0:
             name = self.remove_prefix(symbol.ident, True)
         else:
             name = self.remove_prefix(symbol.ident)
-        callback = Callback(name, retval, list(parameters), symbol.ident)
+        callback = Callback(name, retval, parameters, symbol.ident)
 
         return callback
 
@@ -601,7 +718,7 @@ class Transformer(object):
                                                      self.ctype_of(ptype),
                                                      names, **kwargs)
         elif isinstance(ptype, basestring):
-            return self.resolve_type_name_full(ptype, None, names, **kwargs)
+            return self.resolve_type_name_full(ptype, ptype, names, **kwargs)
         else:
             raise AssertionError("Unhandled param: %r" % (ptype, ))
         return ptype
@@ -621,3 +738,8 @@ class Transformer(object):
             else:
                 break
         return type_name
+
+    def iter_enums(self):
+        for node in self._namespace.nodes:
+            if isinstance(node, Enum):
+                yield node