Bug 552065: Add deprecation information to GIR
[gnome.gobject-introspection] / giscanner / transformer.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 #
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18 # 02110-1301, USA.
19 #
20
21 import os
22
23 from giscanner.ast import (Callback, Enum, Function, Namespace, Member,
24                            Parameter, Return, Sequence, Struct, Field,
25                            Type, Alias, Interface, Class, Node, Union,
26                            type_name_from_ctype, type_names)
27 from giscanner.config import DATADIR
28 from .glibast import GLibBoxed
29 from giscanner.sourcescanner import (
30     SourceSymbol, ctype_name, CTYPE_POINTER,
31     CTYPE_BASIC_TYPE, CTYPE_UNION, CTYPE_ARRAY, CTYPE_TYPEDEF,
32     CTYPE_VOID, CTYPE_ENUM, CTYPE_FUNCTION, CTYPE_STRUCT,
33     CSYMBOL_TYPE_FUNCTION, CSYMBOL_TYPE_TYPEDEF, CSYMBOL_TYPE_STRUCT,
34     CSYMBOL_TYPE_ENUM, CSYMBOL_TYPE_UNION, CSYMBOL_TYPE_OBJECT,
35     CSYMBOL_TYPE_MEMBER)
36 from .odict import odict
37 from .utils import strip_common_prefix, to_underscores
38
39 _xdg_data_dirs = [x for x in os.environ.get('XDG_DATA_DIRS', '').split(':') \
40                       + [DATADIR, '/usr/share'] if x]
41
42
43 class SkipError(Exception):
44     pass
45
46
47 class Names(object):
48     names = property(lambda self: self._names)
49     aliases = property(lambda self: self._aliases)
50     type_names = property(lambda self: self._type_names)
51     ctypes = property(lambda self: self._ctypes)
52
53     def __init__(self):
54         super(Names, self).__init__()
55         self._names = odict() # Maps from GIName -> (namespace, node)
56         self._aliases = {} # Maps from GIName -> GIName
57         self._type_names = {} # Maps from GTName -> (namespace, node)
58         self._ctypes = {} # Maps from CType -> (namespace, node)
59
60
61 class Transformer(object):
62
63     def __init__(self, generator, namespace_name):
64         self.generator = generator
65         self._namespace = Namespace(namespace_name)
66         self._names = Names()
67         self._typedefs_ns = {}
68         self._strip_prefix = ''
69         self._includes = set()
70         self._includepaths = []
71
72     def get_names(self):
73         return self._names
74
75     def get_includes(self):
76         return self._includes
77
78     def set_strip_prefix(self, strip_prefix):
79         self._strip_prefix = strip_prefix
80
81     def parse(self):
82         nodes = []
83         for symbol in self.generator.get_symbols():
84             node = self._traverse_one(symbol)
85             self._add_node(node)
86         return self._namespace
87
88     def register_include(self, filename):
89         (path, suffix) = os.path.splitext(filename)
90         name = os.path.basename(path)
91         if name in self._includes:
92             return
93         if suffix == '':
94             suffix = '.gir'
95             filename = path + suffix
96         if suffix == '.gir':
97             source = filename
98             if not os.path.exists(filename):
99                 searchdirs = [os.path.join(d, 'gir') for d \
100                                   in _xdg_data_dirs]
101                 searchdirs.extend(self._includepaths)
102                 source = None
103                 for d in searchdirs:
104                     source = os.path.join(d, filename)
105                     if os.path.exists(source):
106                         break
107                     source = None
108             if not source:
109                 raise ValueError("Couldn't find include %r (search path: %r)"\
110                                      % (filename, searchdirs))
111             d = os.path.dirname(source)
112             if d not in self._includepaths:
113                 self._includepaths.append(d)
114             self._includes.add(name)
115             from .girparser import GIRParser
116             parser = GIRParser(source)
117         else:
118             raise NotImplementedError(filename)
119         for include in parser.get_includes():
120             self.register_include(include)
121         nsname = parser.get_namespace_name()
122         for node in parser.get_nodes():
123             if isinstance(node, Alias):
124                 self._names.aliases[node.name] = (nsname, node)
125             elif isinstance(node, (GLibBoxed, Interface, Class)):
126                 self._names.type_names[node.type_name] = (nsname, node)
127             self._names.names[node.name] = (nsname, node)
128             if hasattr(node, 'ctype'):
129                 self._names.ctypes[node.ctype] = (nsname, node)
130             elif hasattr(node, 'symbol'):
131                 self._names.ctypes[node.symbol] = (nsname, node)
132
133     def strip_namespace_object(self, name):
134         prefix = self._namespace.name.lower()
135         if len(name) > len(prefix) and name.lower().startswith(prefix):
136             return name[len(prefix):]
137         return self._remove_prefix(name)
138
139     # Private
140
141     def _add_node(self, node):
142         if node is None:
143             return
144         if node.name.startswith('_'):
145             return
146         self._namespace.nodes.append(node)
147         self._names.names[node.name] = (None, node)
148
149     def _strip_namespace_func(self, name):
150         prefix = self._namespace.name.lower() + '_'
151         if name.lower().startswith(prefix):
152             name = name[len(prefix):]
153         else:
154             prefix = to_underscores(self._namespace.name).lower() + '_'
155             if name.lower().startswith(prefix):
156                 name = name[len(prefix):]
157         return self._remove_prefix(name, isfunction=True)
158
159     def _remove_prefix(self, name, isfunction=False):
160         # when --strip-prefix=g:
161         #   GHashTable -> HashTable
162         #   g_hash_table_new -> hash_table_new
163         prefix = self._strip_prefix.lower()
164         if isfunction:
165             prefix += '_'
166         if name.lower().startswith(prefix):
167             name = name[len(prefix):]
168
169         while name.startswith('_'):
170             name = name[1:]
171         return name
172
173     def _traverse_one(self, symbol, stype=None):
174         assert isinstance(symbol, SourceSymbol), symbol
175
176         if stype is None:
177             stype = symbol.type
178         if stype == CSYMBOL_TYPE_FUNCTION:
179             try:
180                 return self._create_function(symbol)
181             except SkipError:
182                 return
183         elif stype == CSYMBOL_TYPE_TYPEDEF:
184             return self._create_typedef(symbol)
185         elif stype == CSYMBOL_TYPE_STRUCT:
186             return self._create_struct(symbol)
187         elif stype == CSYMBOL_TYPE_ENUM:
188             return self._create_enum(symbol)
189         elif stype == CSYMBOL_TYPE_OBJECT:
190             return self._create_object(symbol)
191         elif stype == CSYMBOL_TYPE_MEMBER:
192             return self._create_member(symbol)
193         elif stype == CSYMBOL_TYPE_UNION:
194             return self._create_union(symbol)
195         else:
196             raise NotImplementedError(
197                 'Transformer: unhandled symbol: %r' % (symbol, ))
198
199     def _create_enum(self, symbol):
200         members = []
201         for child in symbol.base_type.child_list:
202             name = strip_common_prefix(symbol.ident, child.ident).lower()
203             members.append(Member(name,
204                                   child.const_int,
205                                   child.ident))
206
207         enum_name = self.strip_namespace_object(symbol.ident)
208         enum_name = symbol.ident[-len(enum_name):]
209         enum_name = self._remove_prefix(enum_name)
210         enum = Enum(enum_name, symbol.ident, members)
211         self._names.type_names[symbol.ident] = (None, enum)
212         return enum
213
214     def _create_object(self, symbol):
215         return Member(symbol.ident, symbol.base_type.name,
216                       symbol.ident)
217
218     def _create_function(self, symbol):
219         directives = symbol.directives()
220         parameters = list(self._create_parameters(
221             symbol.base_type, directives))
222         return_ = self._create_return(symbol.base_type.base_type,
223                                       directives.get('return', []))
224         name = self._strip_namespace_func(symbol.ident)
225         func = Function(name, return_, parameters, symbol.ident)
226         deprecated = directives.get('deprecated', False)
227         if deprecated:
228             try:
229                 # Split out gtk-doc version
230                 func.deprecated = deprecated[0].split(':', 1)
231             except ValueError, e:
232                 # No version, just include str
233                 func.deprecated = (None, deprecated[0])
234         return func
235
236     def _create_source_type(self, source_type):
237         if source_type is None:
238             return 'None'
239         if source_type.type == CTYPE_VOID:
240             value = 'void'
241         elif source_type.type == CTYPE_BASIC_TYPE:
242             value = source_type.name
243         elif source_type.type == CTYPE_TYPEDEF:
244             value = source_type.name
245         elif source_type.type == CTYPE_ARRAY:
246             return self._create_source_type(source_type.base_type)
247         elif source_type.type == CTYPE_POINTER:
248             value = self._create_source_type(source_type.base_type) + '*'
249         else:
250             print 'TRANSFORMER: Unhandled source type %r' % (
251                 source_type, )
252             value = 'any'
253         return value
254
255     def _create_parameters(self, base_type, options=None):
256         if not options:
257             options = {}
258         for child in base_type.child_list:
259             yield self._create_parameter(
260                 child, options.get(child.ident, []))
261
262     def _create_member(self, symbol):
263         ctype = symbol.base_type.type
264         if (ctype == CTYPE_POINTER and
265             symbol.base_type.base_type.type == CTYPE_FUNCTION):
266             node = self._create_callback(symbol)
267         else:
268             ftype = self._create_type(symbol.base_type)
269             node = Field(symbol.ident, ftype, symbol.ident)
270         return node
271
272     def _create_typedef(self, symbol):
273         ctype = symbol.base_type.type
274         if (ctype == CTYPE_POINTER and
275             symbol.base_type.base_type.type == CTYPE_FUNCTION):
276             node = self._create_callback(symbol)
277         elif ctype == CTYPE_STRUCT:
278             node = self._create_typedef_struct(symbol)
279         elif ctype == CTYPE_UNION:
280             node = self._create_typedef_union(symbol)
281         elif ctype == CTYPE_ENUM:
282             return self._create_enum(symbol)
283         elif ctype in (CTYPE_TYPEDEF,
284                        CTYPE_POINTER,
285                        CTYPE_BASIC_TYPE,
286                        CTYPE_VOID):
287             name = self.strip_namespace_object(symbol.ident)
288             if symbol.base_type.name:
289                 target = self.strip_namespace_object(symbol.base_type.name)
290             else:
291                 target = 'none'
292             if name in type_names:
293                 return None
294             return Alias(name, target, ctype=symbol.ident)
295         else:
296             raise NotImplementedError(
297                 "symbol %r of type %s" % (symbol.ident, ctype_name(ctype)))
298         return node
299
300     def _create_type(self, source_type):
301         ctype = self._create_source_type(source_type)
302         if ctype == 'va_list':
303             raise SkipError
304         # FIXME: FILE* should not be skipped, it should be handled
305         #        properly instead
306         elif ctype == 'FILE*':
307             raise SkipError
308         type_name = type_name_from_ctype(ctype)
309         type_name = type_name.replace('*', '')
310         resolved_type_name = self.resolve_type_name(type_name)
311         return Type(resolved_type_name, ctype)
312
313     def _create_parameter(self, symbol, options):
314         ptype = self._create_type(symbol.base_type)
315         param = Parameter(symbol.ident, ptype)
316         for option in options:
317             if option in ['in-out', 'inout']:
318                 param.direction = 'inout'
319             elif option == 'in':
320                 param.direction = 'in'
321             elif option == 'out':
322                 param.direction = 'out'
323             elif option == 'callee-owns':
324                 param.transfer = True
325             elif option == 'allow-none':
326                 param.allow_none = True
327             else:
328                 print 'Unhandled parameter annotation option: %s' % (
329                     option, )
330         return param
331
332     def _create_return(self, source_type, options=None):
333         if not options:
334             options = []
335         rtype = self._create_type(source_type)
336         rtype = self.resolve_param_type(rtype)
337         return_ = Return(rtype)
338         for option in options:
339             if option == 'caller-owns':
340                 return_.transfer = True
341             elif option.startswith('seq '):
342                 value, element_options = option[3:].split(None, 2)
343                 c_element_type = self._parse_type_annotation(value)
344                 element_type = c_element_type.replace('*', '')
345                 element_type = self.resolve_type_name(element_type,
346                                                       c_element_type)
347                 seq = Sequence(rtype.name,
348                                type_name_from_ctype(rtype.name),
349                                element_type)
350                 seq.transfer = True
351                 return_.type = seq
352             else:
353                 print 'Unhandled parameter annotation option: %s' % (
354                     option, )
355         return return_
356
357     def _create_typedef_struct(self, symbol):
358         name = self.strip_namespace_object(symbol.ident)
359         struct = Struct(name, symbol.ident)
360         self._typedefs_ns[symbol.ident] = struct
361         return struct
362
363     def _create_typedef_union(self, symbol):
364         name = self._remove_prefix(symbol.ident)
365         name = self.strip_namespace_object(name)
366         union = Union(name, symbol.ident)
367         self._typedefs_ns[symbol.ident] = union
368         return union
369
370     def _create_struct(self, symbol):
371         struct = self._typedefs_ns.get(symbol.ident, None)
372         if struct is None:
373             # This is a bit of a hack; really we should try
374             # to resolve through the typedefs to find the real
375             # name
376             if symbol.ident.startswith('_'):
377                 name = symbol.ident[1:]
378             else:
379                 name = symbol.ident
380             name = self.strip_namespace_object(name)
381             name = self.resolve_type_name(name)
382             struct = Struct(name, symbol.ident)
383
384         for child in symbol.base_type.child_list:
385             field = self._traverse_one(child)
386             if field:
387                 struct.fields.append(field)
388
389         return struct
390
391     def _create_union(self, symbol):
392         union = self._typedefs_ns.get(symbol.ident, None)
393         if union is None:
394             # This is a bit of a hack; really we should try
395             # to resolve through the typedefs to find the real
396             # name
397             if symbol.ident.startswith('_'):
398                 name = symbol.ident[1:]
399             else:
400                 name = symbol.ident
401             name = self.strip_namespace_object(name)
402             name = self.resolve_type_name(name)
403             union = Union(name, symbol.ident)
404
405         for child in symbol.base_type.child_list:
406             field = self._traverse_one(child)
407             if field:
408                 union.fields.append(field)
409
410         return union
411
412     def _create_callback(self, symbol):
413         parameters = self._create_parameters(symbol.base_type.base_type)
414         retval = self._create_return(symbol.base_type.base_type.base_type)
415         if symbol.ident.find('_') > 0:
416             name = self._strip_namespace_func(symbol.ident)
417         else:
418             name = self.strip_namespace_object(symbol.ident)
419         return Callback(name, retval, list(parameters), symbol.ident)
420
421     def _parse_type_annotation(self, annotation):
422         if (annotation[0] == "[" and
423             annotation[-1] == "]"):
424             return Sequence(self._parse_type_annotation(annotation[1:-1]))
425         return annotation
426
427     def _typepair_to_str(self, item):
428         nsname, item = item
429         if nsname is None:
430             return item.name
431         return '%s.%s' % (nsname, item.name)
432
433     def _resolve_type_name_1(self, type_name, ctype, names):
434         # First look using the built-in names
435         if ctype:
436             try:
437                 return type_names[ctype]
438             except KeyError, e:
439                 pass
440         try:
441             return type_names[type_name]
442         except KeyError, e:
443             pass
444         type_name = self.strip_namespace_object(type_name)
445         resolved = names.aliases.get(type_name)
446         if resolved:
447             return self._typepair_to_str(resolved)
448         resolved = names.names.get(type_name)
449         if resolved:
450             return self._typepair_to_str(resolved)
451         if ctype:
452             ctype = ctype.replace('*', '')
453             resolved = names.ctypes.get(ctype)
454             if resolved:
455                 return self._typepair_to_str(resolved)
456         resolved = names.type_names.get(type_name)
457         if resolved:
458             return self._typepair_to_str(resolved)
459         raise KeyError("failed to find %r" % (type_name, ))
460
461     def resolve_type_name_full(self, type_name, ctype,
462                                names):
463         try:
464             return self._resolve_type_name_1(type_name, ctype, names)
465         except KeyError, e:
466             try:
467                 return self._resolve_type_name_1(type_name, ctype, self._names)
468             except KeyError, e:
469                 return type_name
470
471     def resolve_type_name(self, type_name, ctype=None):
472         try:
473             return self.resolve_type_name_full(type_name, ctype, self._names)
474         except KeyError, e:
475             return type_name
476
477     def gtypename_to_giname(self, gtname, names):
478         resolved = names.type_names.get(gtname)
479         if resolved:
480             return self._typepair_to_str(resolved)
481         resolved = self._names.type_names.get(gtname)
482         if resolved:
483             return self._typepair_to_str(resolved)
484         raise KeyError("Failed to resolve GType name: %r" % (gtname, ))
485
486     def ctype_of(self, obj):
487         if hasattr(obj, 'ctype'):
488             return obj.ctype
489         elif hasattr(obj, 'symbol'):
490             return obj.symbol
491         else:
492             return None
493
494     def resolve_param_type_full(self, ptype, names):
495         if isinstance(ptype, Sequence):
496             ptype.element_type = \
497                 self.resolve_param_type_full(ptype.element_type, names)
498         elif isinstance(ptype, Node):
499             ptype.name = self.resolve_type_name_full(ptype.name,
500                                                      self.ctype_of(ptype),
501                                                      names)
502         elif isinstance(ptype, basestring):
503             return self.resolve_type_name_full(ptype, None, names)
504         else:
505             raise AssertionError("Unhandled param: %r" % (ptype, ))
506         return ptype
507
508     def resolve_param_type(self, ptype):
509         try:
510             return self.resolve_param_type_full(ptype, self._names)
511         except KeyError, e:
512             return ptype
513
514     def follow_aliases(self, type_name, names):
515         while True:
516             resolved = names.aliases.get(type_name)
517             if resolved:
518                 (ns, alias) = resolved
519                 type_name = alias.target
520             else:
521                 break
522         return type_name