Bug 556579 - Don't try to resolve names for structs/unions
[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 import re
23
24 from giscanner.ast import (Callback, Enum, Function, Namespace, Member,
25                            Parameter, Return, Array, Struct, Field,
26                            Type, Alias, Interface, Class, Node, Union,
27                            List, Map, Varargs, Constant, type_name_from_ctype,
28                            type_names, default_array_types, default_out_types,
29                            TYPE_STRING)
30 from giscanner.config import DATADIR
31 from .glibast import GLibBoxed
32 from giscanner.sourcescanner import (
33     SourceSymbol, ctype_name, CTYPE_POINTER,
34     CTYPE_BASIC_TYPE, CTYPE_UNION, CTYPE_ARRAY, CTYPE_TYPEDEF,
35     CTYPE_VOID, CTYPE_ENUM, CTYPE_FUNCTION, CTYPE_STRUCT,
36     CSYMBOL_TYPE_FUNCTION, CSYMBOL_TYPE_TYPEDEF, CSYMBOL_TYPE_STRUCT,
37     CSYMBOL_TYPE_ENUM, CSYMBOL_TYPE_UNION, CSYMBOL_TYPE_OBJECT,
38     CSYMBOL_TYPE_MEMBER, CSYMBOL_TYPE_ELLIPSIS, CSYMBOL_TYPE_CONST,
39     TYPE_QUALIFIER_CONST)
40 from .odict import odict
41 from .utils import strip_common_prefix, to_underscores
42
43 _xdg_data_dirs = [x for x in os.environ.get('XDG_DATA_DIRS', '').split(':') \
44                       + [DATADIR, '/usr/share'] if x]
45
46
47 class SkipError(Exception):
48     pass
49
50
51 class Names(object):
52     names = property(lambda self: self._names)
53     aliases = property(lambda self: self._aliases)
54     type_names = property(lambda self: self._type_names)
55     ctypes = property(lambda self: self._ctypes)
56
57     def __init__(self):
58         super(Names, self).__init__()
59         self._names = odict() # Maps from GIName -> (namespace, node)
60         self._aliases = {} # Maps from GIName -> GIName
61         self._type_names = {} # Maps from GTName -> (namespace, node)
62         self._ctypes = {} # Maps from CType -> (namespace, node)
63
64
65 class Transformer(object):
66
67     def __init__(self, generator, namespace_name, namespace_version):
68         self.generator = generator
69         self._namespace = Namespace(namespace_name, namespace_version)
70         self._names = Names()
71         self._typedefs_ns = {}
72         self._strip_prefix = ''
73         self._includes = set()
74         self._includepaths = []
75
76         self._list_ctypes = []
77         self._map_ctypes = []
78
79     def get_names(self):
80         return self._names
81
82     def get_includes(self):
83         return self._includes
84
85     def set_container_types(self, list_ctypes, map_ctypes):
86         self._list_ctypes = list_ctypes
87         self._map_ctypes = map_ctypes
88
89     def set_strip_prefix(self, strip_prefix):
90         self._strip_prefix = strip_prefix
91
92     def parse(self):
93         nodes = []
94         for symbol in self.generator.get_symbols():
95             node = self._traverse_one(symbol)
96             self._add_node(node)
97         return self._namespace
98
99     def set_include_paths(self, paths):
100         self._includepaths = list(paths)
101
102     def register_include(self, include, path=None):
103         if path is None:
104             girname = '%s-%s.gir' % (include.name, include.version)
105             searchdirs = list(self._includepaths)
106             searchdirs.extend([os.path.join(d, 'gir')
107                                for d in _xdg_data_dirs])
108             for d in searchdirs:
109                 path = os.path.join(d, girname)
110                 if os.path.exists(path):
111                     break
112                 path = None
113             if not path:
114                 raise ValueError("Couldn't find include %r (search path: %r)"\
115                                      % (girname, searchdirs))
116         d = os.path.dirname(path)
117         self._includes.add(include)
118         from .girparser import GIRParser
119         parser = GIRParser(path)
120         for include in parser.get_includes():
121             self.register_include(include)
122         nsname = parser.get_namespace().name
123         for node in parser.get_namespace().nodes:
124             if isinstance(node, Alias):
125                 self._names.aliases[node.name] = (nsname, node)
126             elif isinstance(node, (GLibBoxed, Interface, Class)):
127                 self._names.type_names[node.type_name] = (nsname, node)
128             self._names.names[node.name] = (nsname, node)
129             if hasattr(node, 'ctype'):
130                 self._names.ctypes[node.ctype] = (nsname, node)
131             elif hasattr(node, 'symbol'):
132                 self._names.ctypes[node.symbol] = (nsname, node)
133
134     def strip_namespace_object(self, name):
135         prefix = self._namespace.name.lower()
136         if len(name) > len(prefix) and name.lower().startswith(prefix):
137             return name[len(prefix):]
138         return self._remove_prefix(name)
139
140     # Private
141
142     def _add_node(self, node):
143         if node is None:
144             return
145         if node.name.startswith('_'):
146             return
147         self._namespace.nodes.append(node)
148         self._names.names[node.name] = (None, node)
149
150     def _strip_namespace_func(self, name):
151         prefix = self._namespace.name.lower() + '_'
152         if name.lower().startswith(prefix):
153             name = name[len(prefix):]
154         else:
155             prefix = to_underscores(self._namespace.name).lower() + '_'
156             if name.lower().startswith(prefix):
157                 name = name[len(prefix):]
158         return self._remove_prefix(name, isfunction=True)
159
160     def _remove_prefix(self, name, isfunction=False):
161         # when --strip-prefix=g:
162         #   GHashTable -> HashTable
163         #   g_hash_table_new -> hash_table_new
164         prefix = self._strip_prefix.lower()
165         if isfunction:
166             prefix += '_'
167         if name.lower().startswith(prefix):
168             name = name[len(prefix):]
169
170         while name.startswith('_'):
171             name = name[1:]
172         return name
173
174     def _traverse_one(self, symbol, stype=None):
175         assert isinstance(symbol, SourceSymbol), symbol
176
177         if stype is None:
178             stype = symbol.type
179         if stype == CSYMBOL_TYPE_FUNCTION:
180             try:
181                 return self._create_function(symbol)
182             except SkipError:
183                 return
184         elif stype == CSYMBOL_TYPE_TYPEDEF:
185             return self._create_typedef(symbol)
186         elif stype == CSYMBOL_TYPE_STRUCT:
187             return self._create_struct(symbol)
188         elif stype == CSYMBOL_TYPE_ENUM:
189             return self._create_enum(symbol)
190         elif stype == CSYMBOL_TYPE_OBJECT:
191             return self._create_object(symbol)
192         elif stype == CSYMBOL_TYPE_MEMBER:
193             return self._create_member(symbol)
194         elif stype == CSYMBOL_TYPE_UNION:
195             return self._create_union(symbol)
196         elif stype == CSYMBOL_TYPE_CONST:
197             return self._create_const(symbol)
198         else:
199             raise NotImplementedError(
200                 'Transformer: unhandled symbol: %r' % (symbol, ))
201
202     def _create_enum(self, symbol):
203         members = []
204         for child in symbol.base_type.child_list:
205             name = strip_common_prefix(symbol.ident, child.ident).lower()
206             members.append(Member(name,
207                                   child.const_int,
208                                   child.ident))
209
210         enum_name = self.strip_namespace_object(symbol.ident)
211         enum_name = symbol.ident[-len(enum_name):]
212         enum_name = self._remove_prefix(enum_name)
213         enum = Enum(enum_name, symbol.ident, members)
214         self._names.type_names[symbol.ident] = (None, enum)
215         return enum
216
217     def _create_object(self, symbol):
218         return Member(symbol.ident, symbol.base_type.name,
219                       symbol.ident)
220
221     def _parse_deprecated(self, node, directives):
222         deprecated = directives.get('deprecated', False)
223         if deprecated:
224             deprecated_value = deprecated[0]
225             if ':' in deprecated_value:
226                 # Split out gtk-doc version
227                 (node.deprecated_version, node.deprecated) = \
228                     [x.strip() for x in deprecated_value.split(':', 1)]
229             else:
230                 # No version, just include str
231                 node.deprecated = deprecated_value.strip()
232
233     def _pair_array(self, params, array):
234         if not array.type.length_param_name:
235             return
236         target_name = array.type.length_param_name
237         for i, param in enumerate(params):
238             if param.name == array.type.length_param_name:
239                 array.type.length_param_index = i
240                 return
241         raise ValueError("Unmatched length parameter name %r"\
242                              % (target_name, ))
243
244     def _pair_annotations(self, params):
245         names = {}
246         for param in params:
247             if param.name in names:
248                 raise ValueError("Duplicate parameter name %r"\
249                                      % (param.name, ))
250             names[param.name] = 1
251             if isinstance(param.type, Array):
252                 self._pair_array(params, param)
253
254     # We take the annotations from the parser as strings; here we
255     # want to split them into components, so:
256     # (transfer full) -> {'transfer' : [ 'full' ]}
257
258     def _parse_options(self, options):
259         ret = {}
260         ws_re = re.compile(r'\s+')
261         for opt in options:
262             items = ws_re.split(opt)
263             ret[items[0]] = items[1:]
264         return ret
265
266     def _create_function(self, symbol):
267         directives = symbol.directives()
268         parameters = list(self._create_parameters(
269             symbol.base_type, directives))
270         self._pair_annotations(parameters)
271         return_ = self._create_return(symbol.base_type.base_type,
272                                       directives.get('return', {}))
273         name = self._strip_namespace_func(symbol.ident)
274         func = Function(name, return_, parameters, symbol.ident)
275         self._parse_deprecated(func, directives)
276         return func
277
278     def _create_source_type(self, source_type):
279         if source_type is None:
280             return 'None'
281         if source_type.type == CTYPE_VOID:
282             value = 'void'
283         elif source_type.type == CTYPE_BASIC_TYPE:
284             value = source_type.name
285         elif source_type.type == CTYPE_TYPEDEF:
286             value = source_type.name
287         elif source_type.type == CTYPE_ARRAY:
288             return self._create_source_type(source_type.base_type)
289         elif source_type.type == CTYPE_POINTER:
290             value = self._create_source_type(source_type.base_type) + '*'
291         else:
292             print 'TRANSFORMER: Unhandled source type %r' % (
293                 source_type, )
294             value = 'any'
295         return value
296
297     def _create_parameters(self, base_type, directives=None):
298         if directives is None:
299             dirs = {}
300         else:
301             dirs = directives
302         for child in base_type.child_list:
303             yield self._create_parameter(
304                 child, dirs.get(child.ident, {}))
305
306     def _create_member(self, symbol):
307         ctype = symbol.base_type.type
308         if (ctype == CTYPE_POINTER and
309             symbol.base_type.base_type.type == CTYPE_FUNCTION):
310             node = self._create_callback(symbol)
311         else:
312             ftype = self._create_type(symbol.base_type, {})
313             node = Field(symbol.ident, ftype, symbol.ident, symbol.const_int)
314         return node
315
316     def _create_typedef(self, symbol):
317         ctype = symbol.base_type.type
318         if (ctype == CTYPE_POINTER and
319             symbol.base_type.base_type.type == CTYPE_FUNCTION):
320             node = self._create_callback(symbol)
321         elif ctype == CTYPE_STRUCT:
322             node = self._create_typedef_struct(symbol)
323         elif ctype == CTYPE_UNION:
324             node = self._create_typedef_union(symbol)
325         elif ctype == CTYPE_ENUM:
326             return self._create_enum(symbol)
327         elif ctype in (CTYPE_TYPEDEF,
328                        CTYPE_POINTER,
329                        CTYPE_BASIC_TYPE,
330                        CTYPE_VOID):
331             name = self.strip_namespace_object(symbol.ident)
332             if symbol.base_type.name:
333                 target = self.strip_namespace_object(symbol.base_type.name)
334             else:
335                 target = 'none'
336             if name in type_names:
337                 return None
338             return Alias(name, target, ctype=symbol.ident)
339         else:
340             raise NotImplementedError(
341                 "symbol %r of type %s" % (symbol.ident, ctype_name(ctype)))
342         return node
343
344     def _parse_ctype(self, ctype):
345         derefed = ctype.replace('*', '')
346         return type_name_from_ctype(derefed)
347
348     def _create_type(self, source_type, options):
349         ctype = self._create_source_type(source_type)
350         if ctype == 'va_list':
351             raise SkipError()
352         # FIXME: FILE* should not be skipped, it should be handled
353         #        properly instead
354         elif ctype == 'FILE*':
355             raise SkipError
356
357         # Now check for a list/map/array type
358         if ctype in self._list_ctypes:
359             param = options.get('element-type')
360             if param:
361                 contained_type = self._parse_ctype(param[0])
362             else:
363                 contained_type = None
364             return List(ctype.replace('*', ''),
365                         ctype,
366                         contained_type)
367         elif ctype in self._map_ctypes:
368             param = options.get('element-type')
369             if param:
370                 key_type = self._parse_ctype(param[0])
371                 value_type = self._parse_ctype(param[1])
372             else:
373                 key_type = None
374                 value_type = None
375             return Map(ctype.replace('*', ''),
376                        ctype,
377                        key_type, value_type)
378         elif (ctype in default_array_types) or ('array' in options):
379             derefed = ctype[:-1] # strip the *
380             result = Array(ctype,
381                          self._parse_ctype(derefed))
382             array_opts = options.get('array')
383             if array_opts:
384                 (_, len_name) = array_opts[0].split('=')
385                 result.length_param_name = len_name
386             return result
387
388         # string memory management - we just look at 'const'
389         if type_name_from_ctype(ctype) == TYPE_STRING:
390             if source_type.base_type.type_qualifier & TYPE_QUALIFIER_CONST:
391                 options['transfer'] = ['none']
392             else:
393                 options['transfer'] = ['full']
394
395         derefed_name = self._parse_ctype(ctype)
396
397         # deduce direction for some types passed by reference
398         if (not ('out' in options or
399                  'in' in options or
400                  'inout' in options or
401                  'in-out' in options) and
402             source_type.type == CTYPE_POINTER and
403             derefed_name in default_out_types):
404             options['out'] = []
405
406         return Type(derefed_name, ctype)
407
408     def _handle_generic_param_options(self, param, options):
409         for option, data in options.iteritems():
410             if option == 'transfer':
411                 if data:
412                     depth = data[0]
413                     if depth not in ('none', 'container', 'full'):
414                         raise ValueError("Invalid transfer %r" % (depth, ))
415                 else:
416                     depth = 'full'
417                 param.transfer = depth
418
419     def _create_parameter(self, symbol, options):
420         options = self._parse_options(options)
421         if symbol.type == CSYMBOL_TYPE_ELLIPSIS:
422             ptype = Varargs()
423         else:
424             ptype = self._create_type(symbol.base_type, options)
425         param = Parameter(symbol.ident, ptype)
426         for option, data in options.iteritems():
427             if option in ['in-out', 'inout']:
428                 param.direction = 'inout'
429             elif option == 'in':
430                 param.direction = 'in'
431             elif option == 'out':
432                 param.direction = 'out'
433             elif option == 'allow-none':
434                 param.allow_none = True
435             elif option.startswith(('element-type', 'array')):
436                 pass
437             elif option == 'transfer':
438                 pass
439             else:
440                 print 'Unhandled parameter annotation option: %r' % (
441                     option, )
442         self._handle_generic_param_options(param, options)
443         return param
444
445     def _create_return(self, source_type, options=None):
446         if options is None:
447             options_map = {}
448         else:
449             options_map = self._parse_options(options)
450         rtype = self._create_type(source_type, options_map)
451         rtype = self.resolve_param_type(rtype)
452         return_ = Return(rtype)
453         self._handle_generic_param_options(return_, options_map)
454         for option, data in options_map.iteritems():
455             if option == 'transfer':
456                 pass
457             else:
458                 print 'Unhandled return type annotation option: %r' % (
459                     option, )
460         return return_
461
462     def _create_const(self, symbol):
463         name = self._remove_prefix(symbol.ident)
464         name = self._strip_namespace_func(name)
465         if symbol.const_string is None:
466             type_name = 'int'
467             value = symbol.const_int
468         else:
469             type_name = 'utf8'
470             value = symbol.const_string
471         const = Constant(name, type_name, value)
472         return const
473
474     def _create_typedef_struct(self, symbol):
475         name = self.strip_namespace_object(symbol.ident)
476         struct = Struct(name, symbol.ident)
477         self._typedefs_ns[symbol.ident] = struct
478         self._create_struct(symbol)
479         return struct
480
481     def _create_typedef_union(self, symbol):
482         name = self._remove_prefix(symbol.ident)
483         name = self.strip_namespace_object(name)
484         union = Union(name, symbol.ident)
485         self._typedefs_ns[symbol.ident] = union
486         self._create_union(symbol)
487         return union
488
489     def _create_struct(self, symbol):
490         struct = self._typedefs_ns.get(symbol.ident, None)
491         if struct is None:
492             # This is a bit of a hack; really we should try
493             # to resolve through the typedefs to find the real
494             # name
495             if symbol.ident.startswith('_'):
496                 name = symbol.ident[1:]
497             else:
498                 name = symbol.ident
499             name = self.strip_namespace_object(name)
500             struct = Struct(name, symbol.ident)
501
502         for child in symbol.base_type.child_list:
503             field = self._traverse_one(child)
504             if field:
505                 struct.fields.append(field)
506
507         return struct
508
509     def _create_union(self, symbol):
510         union = self._typedefs_ns.get(symbol.ident, None)
511         if union is None:
512             # This is a bit of a hack; really we should try
513             # to resolve through the typedefs to find the real
514             # name
515             if symbol.ident.startswith('_'):
516                 name = symbol.ident[1:]
517             else:
518                 name = symbol.ident
519             name = self.strip_namespace_object(name)
520             union = Union(name, symbol.ident)
521
522         for child in symbol.base_type.child_list:
523             field = self._traverse_one(child)
524             if field:
525                 union.fields.append(field)
526
527         return union
528
529     def _create_callback(self, symbol):
530         parameters = self._create_parameters(symbol.base_type.base_type)
531         retval = self._create_return(symbol.base_type.base_type.base_type)
532         if symbol.ident.find('_') > 0:
533             name = self._strip_namespace_func(symbol.ident)
534         else:
535             name = self.strip_namespace_object(symbol.ident)
536         return Callback(name, retval, list(parameters), symbol.ident)
537
538     def _typepair_to_str(self, item):
539         nsname, item = item
540         if nsname is None:
541             return item.name
542         return '%s.%s' % (nsname, item.name)
543
544     def _resolve_type_name_1(self, type_name, ctype, names):
545         # First look using the built-in names
546         if ctype:
547             try:
548                 return type_names[ctype]
549             except KeyError, e:
550                 pass
551         try:
552             return type_names[type_name]
553         except KeyError, e:
554             pass
555         type_name = self.strip_namespace_object(type_name)
556         resolved = names.aliases.get(type_name)
557         if resolved:
558             return self._typepair_to_str(resolved)
559         resolved = names.names.get(type_name)
560         if resolved:
561             return self._typepair_to_str(resolved)
562         if ctype:
563             ctype = ctype.replace('*', '')
564             resolved = names.ctypes.get(ctype)
565             if resolved:
566                 return self._typepair_to_str(resolved)
567         resolved = names.type_names.get(type_name)
568         if resolved:
569             return self._typepair_to_str(resolved)
570         raise KeyError("failed to find %r" % (type_name, ))
571
572     def resolve_type_name_full(self, type_name, ctype,
573                                names):
574         try:
575             return self._resolve_type_name_1(type_name, ctype, names)
576         except KeyError, e:
577             try:
578                 return self._resolve_type_name_1(type_name, ctype, self._names)
579             except KeyError, e:
580                 return type_name
581
582     def resolve_type_name(self, type_name, ctype=None):
583         try:
584             return self.resolve_type_name_full(type_name, ctype, self._names)
585         except KeyError, e:
586             return type_name
587
588     def gtypename_to_giname(self, gtname, names):
589         resolved = names.type_names.get(gtname)
590         if resolved:
591             return self._typepair_to_str(resolved)
592         resolved = self._names.type_names.get(gtname)
593         if resolved:
594             return self._typepair_to_str(resolved)
595         raise KeyError("Failed to resolve GType name: %r" % (gtname, ))
596
597     def ctype_of(self, obj):
598         if hasattr(obj, 'ctype'):
599             return obj.ctype
600         elif hasattr(obj, 'symbol'):
601             return obj.symbol
602         else:
603             return None
604
605     def resolve_param_type_full(self, ptype, names):
606         if isinstance(ptype, Node):
607             ptype.name = self.resolve_type_name_full(ptype.name,
608                                                      self.ctype_of(ptype),
609                                                      names)
610             if isinstance(ptype, (Array, List)):
611                 if ptype.element_type is not None:
612                     ptype.element_type = \
613                         self.resolve_param_type_full(ptype.element_type, names)
614             if isinstance(ptype, Map):
615                 if ptype.key_type is not None:
616                     ptype.key_type = \
617                         self.resolve_param_type_full(ptype.key_type, names)
618                     ptype.value_type = \
619                         self.resolve_param_type_full(ptype.value_type, names)
620         elif isinstance(ptype, basestring):
621             return self.resolve_type_name_full(ptype, None, names)
622         else:
623             raise AssertionError("Unhandled param: %r" % (ptype, ))
624         return ptype
625
626     def resolve_param_type(self, ptype):
627         try:
628             return self.resolve_param_type_full(ptype, self._names)
629         except KeyError, e:
630             return ptype
631
632     def follow_aliases(self, type_name, names):
633         while True:
634             resolved = names.aliases.get(type_name)
635             if resolved:
636                 (ns, alias) = resolved
637                 type_name = alias.target
638             else:
639                 break
640         return type_name