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