Redo type resolving and validation. Add a couple of new tests. Patch
[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 from giscanner.ast import (Callback, Enum, Function, Namespace, Member,
22                            Parameter, Return, Sequence, Struct, Field,
23                            Type, Alias, Interface, Class,
24                            type_name_from_ctype)
25 from .glibast import GLibBoxed
26 from giscanner.sourcescanner import (
27     SourceSymbol, ctype_name, CTYPE_POINTER,
28     CTYPE_BASIC_TYPE, CTYPE_UNION, CTYPE_ARRAY, CTYPE_TYPEDEF,
29     CTYPE_VOID, CTYPE_ENUM, CTYPE_FUNCTION, CTYPE_STRUCT,
30     CSYMBOL_TYPE_FUNCTION, CSYMBOL_TYPE_TYPEDEF, CSYMBOL_TYPE_STRUCT,
31     CSYMBOL_TYPE_ENUM, CSYMBOL_TYPE_UNION, CSYMBOL_TYPE_OBJECT,
32     CSYMBOL_TYPE_MEMBER)
33 from .utils import strip_common_prefix
34
35
36 class SkipError(Exception):
37     pass
38
39
40 class Transformer(object):
41
42     def __init__(self, generator, namespace_name):
43         self.generator = generator
44         self._namespace = Namespace(namespace_name)
45         self._output_ns = {}
46         self._alias_names = {} # Maps from GIName -> GIName
47         self._type_names = {} # Maps from GTName -> (namespace, node)
48         self._ctype_names = {} # Maps from CType -> (namespace, node)
49         self._typedefs_ns = {}
50         self._strip_prefix = ''
51
52     def get_type_names(self):
53         return self._type_names
54
55     def get_alias_names(self):
56         return self._alias_names
57
58     def set_strip_prefix(self, strip_prefix):
59         self._strip_prefix = strip_prefix
60
61     def parse(self):
62         nodes = []
63         for symbol in self.generator.get_symbols():
64             node = self._traverse_one(symbol)
65             self._add_node(node)
66         return self._namespace
67
68     def register_include(self, filename):
69         if filename.endswith('.gir'):
70             from .girparser import GIRParser
71             parser = GIRParser(filename)
72         elif filename.endswith('.gidl'):
73             from .gidlparser import GIDLParser
74             parser = GIDLParser(filename)
75         else:
76             raise NotImplementedError(filename)
77         nsname = parser.get_namespace_name()
78         for node in parser.get_nodes():
79             if isinstance(node, Alias):
80                 self._alias_names[node.ctype] = (nsname, node)
81             elif isinstance(node, (GLibBoxed, Interface, Class)):
82                 self._type_names[node.type_name] = (nsname, node)
83             elif hasattr(node, 'ctype'):
84                 self._ctype_names[node.ctype] = (nsname, node)
85             elif hasattr(node, 'symbol'):
86                 self._ctype_names[node.symbol] = (nsname, node)
87             else:
88                 self._type_names[node.name] = (nsname, node)
89
90     def strip_namespace_object(self, name):
91         prefix = self._namespace.name.lower()
92         if len(name) > len(prefix) and name.lower().startswith(prefix):
93             return name[len(prefix):]
94         return self._remove_prefix(name)
95
96     # Private
97
98     def _add_node(self, node):
99         if node is None:
100             return
101         if node.name.startswith('_'):
102             return
103         self._namespace.nodes.append(node)
104         self._output_ns[node.name] = node
105
106     def _strip_namespace_func(self, name):
107         prefix = self._namespace.name.lower() + '_'
108         if name.lower().startswith(prefix):
109             name = name[len(prefix):]
110         return self._remove_prefix(name)
111
112     def _remove_prefix(self, name):
113         # when --strip-prefix=g:
114         #   GHashTable -> HashTable
115         #   g_hash_table_new -> hash_table_new
116         if name.lower().startswith(self._strip_prefix.lower()):
117             name = name[len(self._strip_prefix):]
118
119         while name.startswith('_'):
120             name = name[1:]
121         return name
122
123     def _traverse_one(self, symbol, stype=None):
124         assert isinstance(symbol, SourceSymbol), symbol
125
126         if stype is None:
127             stype = symbol.type
128         if stype == CSYMBOL_TYPE_FUNCTION:
129             try:
130                 return self._create_function(symbol)
131             except SkipError:
132                 return
133         elif stype == CSYMBOL_TYPE_TYPEDEF:
134             return self._create_typedef(symbol)
135         elif stype == CSYMBOL_TYPE_STRUCT:
136             return self._create_struct(symbol)
137         elif stype == CSYMBOL_TYPE_ENUM:
138             return self._create_enum(symbol)
139         elif stype == CSYMBOL_TYPE_OBJECT:
140             return self._create_object(symbol)
141         elif stype == CSYMBOL_TYPE_MEMBER:
142             return self._create_member(symbol)
143         elif stype == CSYMBOL_TYPE_UNION:
144             # Unions are not supported
145             pass
146         else:
147             raise NotImplementedError(
148                 'Transformer: unhandled symbol: %r' % (symbol, ))
149
150     def _create_enum(self, symbol):
151         members = []
152         for child in symbol.base_type.child_list:
153             name = strip_common_prefix(symbol.ident, child.ident).lower()
154             members.append(Member(name,
155                                   child.const_int,
156                                   child.ident))
157
158         enum_name = self.strip_namespace_object(symbol.ident)
159         enum_name = symbol.ident[-len(enum_name):]
160         enum_name = self._remove_prefix(enum_name)
161         enum = Enum(enum_name, symbol.ident, members)
162         self._type_names[symbol.ident] = (None, enum)
163         return enum
164
165     def _create_object(self, symbol):
166         return Member(symbol.ident, symbol.base_type.name,
167                       symbol.ident)
168
169     def _create_function(self, symbol):
170         directives = symbol.directives()
171         parameters = list(self._create_parameters(
172             symbol.base_type, directives))
173         return_ = self._create_return(symbol.base_type.base_type,
174                                       directives.get('return', []))
175         name = self._remove_prefix(symbol.ident)
176         name = self._strip_namespace_func(name)
177         return Function(name, return_, parameters, symbol.ident)
178
179     def _create_source_type(self, source_type):
180         if source_type is None:
181             return 'None'
182         if source_type.type == CTYPE_VOID:
183             value = 'void'
184         elif source_type.type == CTYPE_BASIC_TYPE:
185             value = source_type.name
186         elif source_type.type == CTYPE_TYPEDEF:
187             value = source_type.name
188         elif source_type.type == CTYPE_ARRAY:
189             return self._create_source_type(source_type.base_type)
190         elif source_type.type == CTYPE_POINTER:
191             value = self._create_source_type(source_type.base_type) + '*'
192         else:
193             print 'TRANSFORMER: Unhandled source type %r' % (
194                 source_type, )
195             value = 'any'
196         return value
197
198     def _create_parameters(self, base_type, options=None):
199         if not options:
200             options = {}
201         for child in base_type.child_list:
202             yield self._create_parameter(
203                 child, options.get(child.ident, []))
204
205     def _create_member(self, symbol):
206         ctype = symbol.base_type.type
207         if (ctype == CTYPE_POINTER and
208             symbol.base_type.base_type.type == CTYPE_FUNCTION):
209             node = self._create_callback(symbol)
210         else:
211             ftype = self._create_type(symbol.base_type)
212             node = Field(symbol.ident, ftype, symbol.ident)
213         return node
214
215     def _create_typedef(self, symbol):
216         ctype = symbol.base_type.type
217         if (ctype == CTYPE_POINTER and
218             symbol.base_type.base_type.type == CTYPE_FUNCTION):
219             node = self._create_callback(symbol)
220         elif ctype == CTYPE_STRUCT:
221             node = self._create_typedef_struct(symbol)
222         elif ctype == CTYPE_ENUM:
223             return self._create_enum(symbol)
224         elif ctype in (CTYPE_TYPEDEF,
225                        CTYPE_POINTER,
226                        CTYPE_BASIC_TYPE,
227                        CTYPE_UNION,
228                        CTYPE_VOID):
229             if symbol.base_type.name:
230                 name = self.strip_namespace_object(symbol.ident)
231                 target = self.strip_namespace_object(symbol.base_type.name)
232                 return Alias(name, target, ctype=symbol.ident)
233             return None
234         else:
235             raise NotImplementedError(
236                 "symbol %r of type %s" % (symbol.ident, ctype_name(ctype)))
237         return node
238
239     def _create_type(self, source_type):
240         ctype = self._create_source_type(source_type)
241         if ctype == 'va_list':
242             raise SkipError
243         # FIXME: FILE* should not be skipped, it should be handled
244         #        properly instead
245         elif ctype == 'FILE*':
246             raise SkipError
247         type_name = type_name_from_ctype(ctype)
248         resolved_type_name = self.resolve_type_name(type_name)
249         return Type(resolved_type_name, ctype)
250
251     def _create_parameter(self, symbol, options):
252         ptype = self._create_type(symbol.base_type)
253         param = Parameter(symbol.ident, ptype)
254         for option in options:
255             if option in ['in-out', 'inout']:
256                 param.direction = 'inout'
257             elif option == 'in':
258                 param.direction = 'in'
259             elif option == 'out':
260                 param.direction = 'out'
261             elif option == 'callee-owns':
262                 param.transfer = True
263             elif option == 'allow-none':
264                 param.allow_none = True
265             else:
266                 print 'Unhandled parameter annotation option: %s' % (
267                     option, )
268         return param
269
270     def _create_return(self, source_type, options=None):
271         if not options:
272             options = []
273         rtype = self._create_type(source_type)
274         rtype = self.resolve_param_type(rtype)
275         return_ = Return(rtype)
276         for option in options:
277             if option == 'caller-owns':
278                 return_.transfer = True
279             elif option.startswith('seq '):
280                 value, element_options = option[3:].split(None, 2)
281                 element_type = self._parse_type_annotation(value)
282                 seq = Sequence(rtype.name,
283                                type_name_from_ctype(rtype.name),
284                                element_type)
285                 seq.transfer = True
286                 return_.type = seq
287             else:
288                 print 'Unhandled parameter annotation option: %s' % (
289                     option, )
290         return return_
291
292     def _create_typedef_struct(self, symbol):
293         name = self._remove_prefix(symbol.ident)
294         name = self.strip_namespace_object(name)
295         struct = Struct(name, symbol.ident)
296         self._typedefs_ns[symbol.ident] = struct
297         return struct
298
299     def _create_struct(self, symbol):
300         struct = self._typedefs_ns.get(symbol.ident, None)
301         if struct is None:
302             # This is a bit of a hack; really we should try
303             # to resolve through the typedefs to find the real
304             # name
305             if symbol.ident.startswith('_'):
306                 name = symbol.ident[1:]
307             else:
308                 name = symbol.ident
309             name = self._remove_prefix(name)
310             name = self.strip_namespace_object(name)
311             name = self.resolve_type_name(name)
312             struct = Struct(name, symbol.ident)
313
314         for child in symbol.base_type.child_list:
315             field = self._traverse_one(child)
316             if field:
317                 struct.fields.append(field)
318
319         return struct
320
321     def _create_callback(self, symbol):
322         parameters = self._create_parameters(symbol.base_type.base_type)
323         retval = self._create_return(symbol.base_type.base_type.base_type)
324         name = self.strip_namespace_object(symbol.ident)
325         return Callback(name, retval, list(parameters), symbol.ident)
326
327     def _parse_type_annotation(self, annotation):
328         if (annotation[0] == "[" and
329             annotation[-1] == "]"):
330             return Sequence(self._parse_type_annotation(annotation[1:-1]))
331         return annotation
332
333     def _typepair_to_str(self, item):
334         nsname, item = item
335         if nsname is None:
336             return item.name
337         return '%s.%s' % (nsname, item.name)
338
339     def resolve_type_name(self, type_name, ctype=None):
340         type_name = type_name.replace('*', '')
341         type_name = self.strip_namespace_object(type_name)
342         resolved = self._alias_names.get(type_name)
343         if resolved:
344             return self._typepair_to_str(resolved)
345         resolved = self._type_names.get(type_name)
346         if resolved:
347             return self._typepair_to_str(resolved)
348         if ctype:
349             ctype = ctype.replace('*', '')
350             resolved = self._ctype_names.get(ctype)
351             if resolved:
352                 return self._typepair_to_str(resolved)
353         return type_name
354
355     def ctype_of(self, obj):
356         if hasattr(obj, 'ctype'):
357             return obj.ctype
358         elif hasattr(obj, 'symbol'):
359             return obj.symbol
360         else:
361             return None
362
363     def resolve_param_type(self, ptype):
364         ptype.name = self.resolve_type_name(ptype.name,
365                                             self.ctype_of(ptype))
366         return ptype