We are handling those return types, silly
[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     # Private
135
136     def _add_node(self, node):
137         if node is None:
138             return
139         if node.name.startswith('_'):
140             return
141         self._namespace.nodes.append(node)
142         self._names.names[node.name] = (None, node)
143
144     def _strip_namespace_func(self, name):
145         prefix = self._namespace.name.lower() + '_'
146         if name.lower().startswith(prefix):
147             name = name[len(prefix):]
148         else:
149             prefix = to_underscores(self._namespace.name).lower() + '_'
150             if name.lower().startswith(prefix):
151                 name = name[len(prefix):]
152         return self.remove_prefix(name, isfunction=True)
153
154     def remove_prefix(self, name, isfunction=False):
155         # when --strip-prefix=g:
156         #   GHashTable -> HashTable
157         #   g_hash_table_new -> hash_table_new
158         prefix = self._strip_prefix.lower()
159         if isfunction:
160             prefix += '_'
161         if len(name) > len(prefix) and name.lower().startswith(prefix):
162             name = name[len(prefix):]
163
164         while name.startswith('_'):
165             name = name[1:]
166         return name
167
168     def _traverse_one(self, symbol, stype=None):
169         assert isinstance(symbol, SourceSymbol), symbol
170
171         if stype is None:
172             stype = symbol.type
173         if stype == CSYMBOL_TYPE_FUNCTION:
174             try:
175                 return self._create_function(symbol)
176             except SkipError:
177                 return
178         elif stype == CSYMBOL_TYPE_TYPEDEF:
179             return self._create_typedef(symbol)
180         elif stype == CSYMBOL_TYPE_STRUCT:
181             return self._create_struct(symbol)
182         elif stype == CSYMBOL_TYPE_ENUM:
183             return self._create_enum(symbol)
184         elif stype == CSYMBOL_TYPE_OBJECT:
185             return self._create_object(symbol)
186         elif stype == CSYMBOL_TYPE_MEMBER:
187             return self._create_member(symbol)
188         elif stype == CSYMBOL_TYPE_UNION:
189             return self._create_union(symbol)
190         elif stype == CSYMBOL_TYPE_CONST:
191             return self._create_const(symbol)
192         else:
193             raise NotImplementedError(
194                 'Transformer: unhandled symbol: %r' % (symbol, ))
195
196     def _create_enum(self, symbol):
197         members = []
198         for child in symbol.base_type.child_list:
199             name = strip_common_prefix(symbol.ident, child.ident).lower()
200             members.append(Member(name,
201                                   child.const_int,
202                                   child.ident))
203
204         enum_name = self.remove_prefix(symbol.ident)
205         enum = Enum(enum_name, symbol.ident, members)
206         self._names.type_names[symbol.ident] = (None, enum)
207         return enum
208
209     def _create_object(self, symbol):
210         return Member(symbol.ident, symbol.base_type.name,
211                       symbol.ident)
212
213     def _parse_deprecated(self, node, directives):
214         deprecated = directives.get('deprecated', False)
215         if deprecated:
216             deprecated_value = deprecated[0]
217             if ':' in deprecated_value:
218                 # Split out gtk-doc version
219                 (node.deprecated_version, node.deprecated) = \
220                     [x.strip() for x in deprecated_value.split(':', 1)]
221             else:
222                 # No version, just include str
223                 node.deprecated = deprecated_value.strip()
224
225     def _pair_array(self, params, array):
226         if not array.type.length_param_name:
227             return
228         target_name = array.type.length_param_name
229         for i, param in enumerate(params):
230             if param.name == array.type.length_param_name:
231                 array.type.length_param_index = i
232                 return
233         raise ValueError("Unmatched length parameter name %r"\
234                              % (target_name, ))
235
236     def _pair_annotations(self, params):
237         names = {}
238         for param in params:
239             if param.name in names:
240                 raise ValueError("Duplicate parameter name %r"\
241                                      % (param.name, ))
242             names[param.name] = 1
243             if isinstance(param.type, Array):
244                 self._pair_array(params, param)
245
246     # We take the annotations from the parser as strings; here we
247     # want to split them into components, so:
248     # (transfer full) -> {'transfer' : [ 'full' ]}
249
250     def _parse_options(self, options):
251         ret = {}
252         ws_re = re.compile(r'\s+')
253         for opt in options:
254             items = ws_re.split(opt)
255             ret[items[0]] = items[1:]
256         return ret
257
258     def _create_function(self, symbol):
259         directives = symbol.directives()
260         parameters = list(self._create_parameters(
261             symbol.base_type, directives))
262         self._pair_annotations(parameters)
263         return_ = self._create_return(symbol.base_type.base_type,
264                                       directives.get('return', {}))
265         name = self._strip_namespace_func(symbol.ident)
266         func = Function(name, return_, parameters, symbol.ident)
267         self._parse_deprecated(func, directives)
268         return func
269
270     def _create_source_type(self, source_type):
271         if source_type is None:
272             return 'None'
273         if source_type.type == CTYPE_VOID:
274             value = 'void'
275         elif source_type.type == CTYPE_BASIC_TYPE:
276             value = source_type.name
277         elif source_type.type == CTYPE_TYPEDEF:
278             value = source_type.name
279         elif source_type.type == CTYPE_ARRAY:
280             return self._create_source_type(source_type.base_type)
281         elif source_type.type == CTYPE_POINTER:
282             value = self._create_source_type(source_type.base_type) + '*'
283         else:
284             print 'TRANSFORMER: Unhandled source type %r' % (
285                 source_type, )
286             value = 'any'
287         return value
288
289     def _create_parameters(self, base_type, directives=None):
290         if directives is None:
291             dirs = {}
292         else:
293             dirs = directives
294         for child in base_type.child_list:
295             yield self._create_parameter(
296                 child, dirs.get(child.ident, {}))
297
298     def _create_member(self, symbol):
299         ctype = symbol.base_type.type
300         if (ctype == CTYPE_POINTER and
301             symbol.base_type.base_type.type == CTYPE_FUNCTION):
302             node = self._create_callback(symbol)
303         else:
304             ftype = self._create_type(symbol.base_type, {})
305             # Fields are assumed to be read-write
306             # (except for Objects, see also glibtransformer.py)
307             node = Field(symbol.ident, ftype, symbol.ident,
308                        readable=True, writable=True, bits=symbol.const_int)
309         return node
310
311     def _create_typedef(self, symbol):
312         ctype = symbol.base_type.type
313         if (ctype == CTYPE_POINTER and
314             symbol.base_type.base_type.type == CTYPE_FUNCTION):
315             node = self._create_callback(symbol)
316         elif ctype == CTYPE_STRUCT:
317             node = self._create_typedef_struct(symbol)
318         elif ctype == CTYPE_UNION:
319             node = self._create_typedef_union(symbol)
320         elif ctype == CTYPE_ENUM:
321             return self._create_enum(symbol)
322         elif ctype in (CTYPE_TYPEDEF,
323                        CTYPE_POINTER,
324                        CTYPE_BASIC_TYPE,
325                        CTYPE_VOID):
326             name = self.remove_prefix(symbol.ident)
327             if symbol.base_type.name:
328                 target = self.remove_prefix(symbol.base_type.name)
329             else:
330                 target = 'none'
331             if name in type_names:
332                 return None
333             return Alias(name, target, ctype=symbol.ident)
334         else:
335             raise NotImplementedError(
336                 "symbol %r of type %s" % (symbol.ident, ctype_name(ctype)))
337         return node
338
339     def _parse_ctype(self, ctype):
340         # First look up the ctype including any pointers;
341         # a few type names like 'char*' have their own aliases
342         # and we need pointer information for those.
343         firstpass = type_name_from_ctype(ctype)
344
345         # Remove all pointers - we require standard calling
346         # conventions.  For example, an 'int' is always passed by
347         # value (unless it's out or inout).
348         derefed = firstpass.replace('*', '')
349
350         # Canonicalize our type again, this time without the pointer;
351         # this ensures we turn e.g. plain "guint" => "int"
352         return type_name_from_ctype(derefed)
353
354     def _create_type(self, source_type, options):
355         ctype = self._create_source_type(source_type)
356         if ctype == 'va_list':
357             raise SkipError()
358         # FIXME: FILE* should not be skipped, it should be handled
359         #        properly instead
360         elif ctype == 'FILE*':
361             raise SkipError
362
363         # Now check for a list/map/array type
364         if ctype in self._list_ctypes:
365             param = options.get('element-type')
366             if param:
367                 contained_type = self._parse_ctype(param[0])
368             else:
369                 contained_type = None
370             return List(ctype.replace('*', ''),
371                         ctype,
372                         contained_type)
373         elif ctype in self._map_ctypes:
374             param = options.get('element-type')
375             if param:
376                 key_type = self._parse_ctype(param[0])
377                 value_type = self._parse_ctype(param[1])
378             else:
379                 key_type = None
380                 value_type = None
381             return Map(ctype.replace('*', ''),
382                        ctype,
383                        key_type, value_type)
384         elif (ctype in default_array_types) or ('array' in options):
385             derefed = ctype[:-1] # strip the *
386             result = Array(ctype,
387                          self._parse_ctype(derefed))
388             array_opts = options.get('array')
389             if array_opts:
390                 (_, len_name) = array_opts[0].split('=')
391                 result.length_param_name = len_name
392             return result
393
394         # string memory management - we just look at 'const'
395         if type_name_from_ctype(ctype) == TYPE_STRING:
396             if source_type.base_type.type_qualifier & TYPE_QUALIFIER_CONST:
397                 options['transfer'] = ['none']
398             else:
399                 options['transfer'] = ['full']
400
401         derefed_name = self._parse_ctype(ctype)
402
403         # deduce direction for some types passed by reference
404         if (not ('out' in options or
405                  'in' in options or
406                  'inout' in options or
407                  'in-out' in options) and
408             source_type.type == CTYPE_POINTER and
409             derefed_name in default_out_types):
410             options['out'] = []
411
412         return Type(derefed_name, ctype)
413
414     def _handle_generic_param_options(self, param, options):
415         for option, data in options.iteritems():
416             if option == 'transfer':
417                 if data:
418                     depth = data[0]
419                     if depth not in ('none', 'container', 'full'):
420                         raise ValueError("Invalid transfer %r" % (depth, ))
421                 else:
422                     depth = 'full'
423                 param.transfer = depth
424
425     def _create_parameter(self, symbol, options):
426         options = self._parse_options(options)
427         if symbol.type == CSYMBOL_TYPE_ELLIPSIS:
428             ptype = Varargs()
429         else:
430             ptype = self._create_type(symbol.base_type, options)
431         param = Parameter(symbol.ident, ptype)
432         for option, data in options.iteritems():
433             if option in ['in-out', 'inout']:
434                 param.direction = 'inout'
435             elif option == 'in':
436                 param.direction = 'in'
437             elif option == 'out':
438                 param.direction = 'out'
439             elif option == 'allow-none':
440                 param.allow_none = True
441             elif option.startswith(('element-type', 'array')):
442                 pass
443             elif option == 'transfer':
444                 pass
445             else:
446                 print 'Unhandled parameter annotation option: %r' % (
447                     option, )
448         self._handle_generic_param_options(param, options)
449         return param
450
451     def _create_return(self, source_type, options=None):
452         if options is None:
453             options_map = {}
454         else:
455             options_map = self._parse_options(options)
456         rtype = self._create_type(source_type, options_map)
457         rtype = self.resolve_param_type(rtype)
458         return_ = Return(rtype)
459         self._handle_generic_param_options(return_, options_map)
460         for option, data in options_map.iteritems():
461             if option in ('transfer', 'element-type', 'out'):
462                 pass
463             else:
464                 print 'Unhandled return type annotation option: %r' % (
465                     option, )
466         return return_
467
468     def _create_const(self, symbol):
469         name = self.remove_prefix(symbol.ident)
470         if symbol.const_string is None:
471             type_name = 'int'
472             value = symbol.const_int
473         else:
474             type_name = 'utf8'
475             value = symbol.const_string
476         const = Constant(name, type_name, value)
477         return const
478
479     def _create_typedef_struct(self, symbol):
480         name = self.remove_prefix(symbol.ident)
481         struct = Struct(name, symbol.ident)
482         self._typedefs_ns[symbol.ident] = struct
483         self._create_struct(symbol)
484         return struct
485
486     def _create_typedef_union(self, symbol):
487         name = self.remove_prefix(symbol.ident)
488         union = Union(name, symbol.ident)
489         self._typedefs_ns[symbol.ident] = union
490         self._create_union(symbol)
491         return union
492
493     def _create_struct(self, symbol):
494         struct = self._typedefs_ns.get(symbol.ident, None)
495         if struct is None:
496             # This is a bit of a hack; really we should try
497             # to resolve through the typedefs to find the real
498             # name
499             if symbol.ident.startswith('_'):
500                 name = symbol.ident[1:]
501             else:
502                 name = symbol.ident
503             name = self.remove_prefix(name)
504             struct = Struct(name, symbol.ident)
505
506         for child in symbol.base_type.child_list:
507             field = self._traverse_one(child)
508             if field:
509                 struct.fields.append(field)
510
511         return struct
512
513     def _create_union(self, symbol):
514         union = self._typedefs_ns.get(symbol.ident, None)
515         if union is None:
516             # This is a bit of a hack; really we should try
517             # to resolve through the typedefs to find the real
518             # name
519             if symbol.ident.startswith('_'):
520                 name = symbol.ident[1:]
521             else:
522                 name = symbol.ident
523             name = self.remove_prefix(name)
524             union = Union(name, symbol.ident)
525
526         for child in symbol.base_type.child_list:
527             field = self._traverse_one(child)
528             if field:
529                 union.fields.append(field)
530
531         return union
532
533     def _create_callback(self, symbol):
534         parameters = self._create_parameters(symbol.base_type.base_type)
535         retval = self._create_return(symbol.base_type.base_type.base_type)
536         if symbol.ident.find('_') > 0:
537             name = self.remove_prefix(symbol.ident, True)
538         else:
539             name = self.remove_prefix(symbol.ident)
540         return Callback(name, retval, list(parameters), symbol.ident)
541
542     def _typepair_to_str(self, item):
543         nsname, item = item
544         if nsname is None:
545             return item.name
546         return '%s.%s' % (nsname, item.name)
547
548     def _resolve_type_name_1(self, type_name, ctype, names):
549         # First look using the built-in names
550         if ctype:
551             try:
552                 return type_names[ctype]
553             except KeyError, e:
554                 pass
555         try:
556             return type_names[type_name]
557         except KeyError, e:
558             pass
559         type_name = self.remove_prefix(type_name)
560         resolved = names.aliases.get(type_name)
561         if resolved:
562             return self._typepair_to_str(resolved)
563         resolved = names.names.get(type_name)
564         if resolved:
565             return self._typepair_to_str(resolved)
566         if ctype:
567             ctype = ctype.replace('*', '')
568             resolved = names.ctypes.get(ctype)
569             if resolved:
570                 return self._typepair_to_str(resolved)
571         resolved = names.type_names.get(type_name)
572         if resolved:
573             return self._typepair_to_str(resolved)
574         raise KeyError("failed to find %r" % (type_name, ))
575
576     def resolve_type_name_full(self, type_name, ctype,
577                                names):
578         try:
579             return self._resolve_type_name_1(type_name, ctype, names)
580         except KeyError, e:
581             try:
582                 return self._resolve_type_name_1(type_name, ctype, self._names)
583             except KeyError, e:
584                 return type_name
585
586     def resolve_type_name(self, type_name, ctype=None):
587         try:
588             return self.resolve_type_name_full(type_name, ctype, self._names)
589         except KeyError, e:
590             return type_name
591
592     def gtypename_to_giname(self, gtname, names):
593         resolved = names.type_names.get(gtname)
594         if resolved:
595             return self._typepair_to_str(resolved)
596         resolved = self._names.type_names.get(gtname)
597         if resolved:
598             return self._typepair_to_str(resolved)
599         raise KeyError("Failed to resolve GType name: %r" % (gtname, ))
600
601     def ctype_of(self, obj):
602         if hasattr(obj, 'ctype'):
603             return obj.ctype
604         elif hasattr(obj, 'symbol'):
605             return obj.symbol
606         else:
607             return None
608
609     def resolve_param_type_full(self, ptype, names):
610         if isinstance(ptype, Node):
611             ptype.name = self.resolve_type_name_full(ptype.name,
612                                                      self.ctype_of(ptype),
613                                                      names)
614             if isinstance(ptype, (Array, List)):
615                 if ptype.element_type is not None:
616                     ptype.element_type = \
617                         self.resolve_param_type_full(ptype.element_type, names)
618             if isinstance(ptype, Map):
619                 if ptype.key_type is not None:
620                     ptype.key_type = \
621                         self.resolve_param_type_full(ptype.key_type, names)
622                     ptype.value_type = \
623                         self.resolve_param_type_full(ptype.value_type, names)
624         elif isinstance(ptype, basestring):
625             return self.resolve_type_name_full(ptype, None, names)
626         else:
627             raise AssertionError("Unhandled param: %r" % (ptype, ))
628         return ptype
629
630     def resolve_param_type(self, ptype):
631         try:
632             return self.resolve_param_type_full(ptype, self._names)
633         except KeyError, e:
634             return ptype
635
636     def follow_aliases(self, type_name, names):
637         while True:
638             resolved = names.aliases.get(type_name)
639             if resolved:
640                 (ns, alias) = resolved
641                 type_name = alias.target
642             else:
643                 break
644         return type_name