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