Implement callbacks as part of struct fields. Fixes #557383
[gnome.gobject-introspection] / giscanner / girwriter.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008  Johan Dahlin
4 # Copyright (C) 2008, 2009 Red Hat, Inc.
5 #
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2 of the License, or (at your option) any later version.
10 #
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # Lesser General Public License for more details.
15 #
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this library; if not, write to the
18 # Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19 # Boston, MA 02111-1307, USA.
20 #
21
22 from __future__ import with_statement
23
24 from .ast import (Alias, Array, Bitfield, Callback, Class, Constant, Enum,
25                   Function, Interface, List, Map, Member, Struct, Union,
26                   Varargs)
27 from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember,
28                       GLibFlags, GLibObject, GLibInterface,
29                       GLibRecord)
30 from .xmlwriter import XMLWriter
31
32
33 class GIRWriter(XMLWriter):
34
35     def __init__(self, namespace, shlibs, includes, pkgs, c_includes, cprefix):
36         super(GIRWriter, self).__init__()
37         self.write_comment(
38 '''This file was automatically generated from C sources - DO NOT EDIT!
39 To affect the contents of this file, edit the original C definitions,
40 and/or use gtk-doc annotations. ''')
41         self._write_repository(namespace, shlibs, includes, pkgs,
42                                c_includes, cprefix)
43
44     def _write_repository(self, namespace, shlibs, includes=None,
45                           packages=None, c_includes=None, cprefix=None):
46         if includes is None:
47             includes = frozenset()
48         if packages is None:
49             packages = frozenset()
50         if c_includes is None:
51             c_includes = frozenset()
52         attrs = [
53             ('version', '1.0'),
54             ('xmlns', 'http://www.gtk.org/introspection/core/1.0'),
55             ('xmlns:c', 'http://www.gtk.org/introspection/c/1.0'),
56             ('xmlns:glib', 'http://www.gtk.org/introspection/glib/1.0'),
57             ]
58         with self.tagcontext('repository', attrs):
59             for include in sorted(includes):
60                 self._write_include(include)
61             for pkg in sorted(set(packages)):
62                 self._write_pkgconfig_pkg(pkg)
63             for c_include in sorted(set(c_includes)):
64                 self._write_c_include(c_include)
65             self._write_namespace(namespace, shlibs, cprefix)
66
67     def _write_include(self, include):
68         attrs = [('name', include.name), ('version', include.version)]
69         self.write_tag('include', attrs)
70
71     def _write_pkgconfig_pkg(self, package):
72         attrs = [('name', package)]
73         self.write_tag('package', attrs)
74
75     def _write_c_include(self, c_include):
76         attrs = [('name', c_include)]
77         self.write_tag('c:include', attrs)
78
79     def _write_namespace(self, namespace, shlibs, cprefix):
80         attrs = [('name', namespace.name),
81                  ('version', namespace.version),
82                  ('shared-library', ','.join(shlibs)),
83                  ('c:prefix', cprefix)]
84         with self.tagcontext('namespace', attrs):
85             # We define a custom sorting function here because
86             # we want aliases to be first.  They're a bit
87             # special because the typelib compiler expands them.
88             def nscmp(a, b):
89                 if isinstance(a, Alias):
90                     if isinstance(b, Alias):
91                         return cmp(a.name, b.name)
92                     else:
93                         return -1
94                 elif isinstance(b, Alias):
95                     return 1
96                 else:
97                     return cmp(a, b)
98             for node in sorted(namespace.nodes, cmp=nscmp):
99                 if not node.skip:
100                     self._write_node(node)
101
102     def _write_node(self, node):
103         if isinstance(node, Function):
104             self._write_function(node)
105         elif isinstance(node, Enum):
106             self._write_enum(node)
107         elif isinstance(node, Bitfield):
108             self._write_bitfield(node)
109         elif isinstance(node, (Class, Interface)):
110             self._write_class(node)
111         elif isinstance(node, Callback):
112             self._write_callback(node)
113         elif isinstance(node, Struct):
114             self._write_record(node)
115         elif isinstance(node, Union):
116             self._write_union(node)
117         elif isinstance(node, GLibBoxed):
118             self._write_boxed(node)
119         elif isinstance(node, Member):
120             # FIXME: atk_misc_instance singleton
121             pass
122         elif isinstance(node, Alias):
123             self._write_alias(node)
124         elif isinstance(node, Constant):
125             self._write_constant(node)
126         else:
127             print 'WRITER: Unhandled node', node
128
129     def _append_version(self, node, attrs):
130         if node.version:
131             attrs.append(('version', node.version))
132
133     def _write_attributes(self, node):
134         for key, value in node.attributes:
135             self.write_tag('attribute', [('name', key), ('value', value)])
136
137     def _append_deprecated(self, node, attrs):
138         if node.deprecated:
139             attrs.append(('deprecated', node.deprecated))
140             if node.deprecated_version:
141                 attrs.append(('deprecated-version',
142                               node.deprecated_version))
143
144     def _append_throws(self, func, attrs):
145         if func.throws:
146             attrs.append(('throws', '1'))
147
148     def _write_alias(self, alias):
149         attrs = [('name', alias.name), ('target', alias.target)]
150         if alias.ctype is not None:
151             attrs.append(('c:type', alias.ctype))
152         self.write_tag('alias', attrs)
153
154     def _write_callable(self, callable, tag_name, extra_attrs):
155         attrs = [('name', callable.name)]
156         attrs.extend(extra_attrs)
157         if callable.doc:
158             attrs.append(('doc', callable.doc))
159         self._append_version(callable, attrs)
160         self._append_deprecated(callable, attrs)
161         self._append_throws(callable, attrs)
162         with self.tagcontext(tag_name, attrs):
163             self._write_attributes(callable)
164             self._write_return_type(callable.retval)
165             self._write_parameters(callable.parameters)
166
167     def _write_function(self, func, tag_name='function'):
168         attrs = [('c:identifier', func.symbol)]
169         self._write_callable(func, tag_name, attrs)
170
171     def _write_method(self, method):
172         self._write_function(method, tag_name='method')
173
174     def _write_static_method(self, method):
175         self._write_function(method, tag_name='function')
176
177     def _write_constructor(self, method):
178         self._write_function(method, tag_name='constructor')
179
180     def _write_return_type(self, return_):
181         if not return_:
182             return
183
184         assert return_.transfer is not None, return_
185
186         attrs = []
187         attrs.append(('transfer-ownership', return_.transfer))
188         if return_.doc:
189             attrs.append(('doc', return_.doc))
190         with self.tagcontext('return-value', attrs):
191             self._write_type(return_.type)
192
193     def _write_parameters(self, parameters):
194         if not parameters:
195             return
196         with self.tagcontext('parameters'):
197             for parameter in parameters:
198                 self._write_parameter(parameter)
199
200     def _write_parameter(self, parameter):
201         assert parameter.transfer is not None, parameter
202
203         attrs = []
204         if parameter.name is not None:
205             attrs.append(('name', parameter.name))
206         if parameter.direction != 'in':
207             attrs.append(('direction', parameter.direction))
208         attrs.append(('transfer-ownership',
209                      parameter.transfer))
210         if parameter.allow_none:
211             attrs.append(('allow-none', '1'))
212         if parameter.scope:
213             attrs.append(('scope', parameter.scope))
214         if parameter.closure_index >= 0:
215             attrs.append(('closure', '%d' % parameter.closure_index))
216         if parameter.destroy_index >= 0:
217             attrs.append(('destroy', '%d' % parameter.destroy_index))
218         if parameter.doc:
219             attrs.append(('doc', parameter.doc))
220         with self.tagcontext('parameter', attrs):
221             self._write_type(parameter.type)
222
223     def _type_to_string(self, ntype):
224         if isinstance(ntype, basestring):
225             return ntype
226         return ntype.name
227
228     def _write_type(self, ntype, relation=None):
229         if isinstance(ntype, basestring):
230             typename = ntype
231             type_cname = None
232         else:
233             typename = ntype.name
234             type_cname = ntype.ctype
235         if isinstance(ntype, Varargs):
236             with self.tagcontext('varargs', []):
237                 pass
238             return
239         if isinstance(ntype, Array):
240             attrs = []
241             if not ntype.zeroterminated:
242                 attrs.append(('zero-terminated', '0'))
243             if ntype.length_param_index >= 0:
244                 attrs.append(('length', '%d' % (ntype.length_param_index, )))
245             attrs.append(('c:type', ntype.ctype))
246             if ntype.size is not None:
247                 attrs.append(('fixed-size', ntype.size))
248             with self.tagcontext('array', attrs):
249                 self._write_type(ntype.element_type)
250             return
251         attrs = [('name', self._type_to_string(ntype))]
252         # FIXME: figure out if type references a basic type
253         #        or a boxed/class/interface etc. and skip
254         #        writing the ctype if the latter.
255         if type_cname is not None:
256             attrs.append(('c:type', type_cname))
257         if isinstance(ntype, List) and ntype.element_type:
258             with self.tagcontext('type', attrs):
259                 self._write_type(ntype.element_type)
260             return
261         if isinstance(ntype, Map) and ntype.key_type:
262             with self.tagcontext('type', attrs):
263                 self._write_type(ntype.key_type)
264                 self._write_type(ntype.value_type)
265             return
266         # Not a special type, just write it out
267         self.write_tag('type', attrs)
268
269     def _write_enum(self, enum):
270         attrs = [('name', enum.name)]
271         if enum.doc:
272             attrs.append(('doc', enum.doc))
273         self._append_version(enum, attrs)
274         self._append_deprecated(enum, attrs)
275         if isinstance(enum, GLibEnum):
276             attrs.extend([('glib:type-name', enum.type_name),
277                           ('glib:get-type', enum.get_type),
278                           ('c:type', enum.ctype)])
279             if enum.error_quark:
280                 attrs.append(('glib:error-quark', enum.error_quark))
281         else:
282             attrs.append(('c:type', enum.symbol))
283
284         with self.tagcontext('enumeration', attrs):
285             self._write_attributes(enum)
286             for member in enum.members:
287                 self._write_member(member)
288
289     def _write_bitfield(self, bitfield):
290         attrs = [('name', bitfield.name)]
291         if bitfield.doc:
292             attrs.append(('doc', bitfield.doc))
293         self._append_version(bitfield, attrs)
294         self._append_deprecated(bitfield, attrs)
295         if isinstance(bitfield, GLibFlags):
296             attrs.extend([('glib:type-name', bitfield.type_name),
297                           ('glib:get-type', bitfield.get_type),
298                           ('c:type', bitfield.ctype)])
299         else:
300             attrs.append(('c:type', bitfield.symbol))
301         with self.tagcontext('bitfield', attrs):
302             self._write_attributes(bitfield)
303             for member in bitfield.members:
304                 self._write_member(member)
305
306     def _write_member(self, member):
307         attrs = [('name', member.name),
308                  ('value', str(member.value)),
309                  ('c:identifier', member.symbol)]
310         if isinstance(member, GLibEnumMember):
311             attrs.append(('glib:nick', member.nick))
312         self.write_tag('member', attrs)
313
314     def _write_constant(self, constant):
315         attrs = [('name', constant.name),
316                  ('value', str(constant.value))]
317         with self.tagcontext('constant', attrs):
318             self._write_type(constant.type)
319
320     def _write_class(self, node):
321         attrs = [('name', node.name),
322                  ('c:type', node.ctype)]
323         if node.doc:
324             attrs.append(('doc', node.doc))
325         self._append_version(node, attrs)
326         self._append_deprecated(node, attrs)
327         if isinstance(node, Class):
328             tag_name = 'class'
329             if node.parent is not None:
330                 attrs.append(('parent', node.parent))
331             if node.is_abstract:
332                 attrs.append(('abstract', '1'))
333         else:
334             tag_name = 'interface'
335         if isinstance(node, (GLibObject, GLibInterface)):
336             attrs.append(('glib:type-name', node.type_name))
337             if node.get_type:
338                 attrs.append(('glib:get-type', node.get_type))
339             if node.glib_type_struct:
340                 attrs.append(('glib:type-struct', node.glib_type_struct.name))
341         with self.tagcontext(tag_name, attrs):
342             self._write_attributes(node)
343             if isinstance(node, GLibObject):
344                 for iface in node.interfaces:
345                     self.write_tag('implements', [('name', iface)])
346             if isinstance(node, Interface):
347                 for iface in node.prerequisites:
348                     self.write_tag('prerequisite', [('name', iface)])
349             if isinstance(node, Class):
350                 for method in node.constructors:
351                     self._write_constructor(method)
352                 for method in node.static_methods:
353                     self._write_static_method(method)
354             for vfunc in node.virtual_methods:
355                 self._write_vfunc(vfunc)
356             for method in node.methods:
357                 self._write_method(method)
358             for prop in node.properties:
359                 self._write_property(prop)
360             for field in node.fields:
361                 self._write_field(field)
362             for signal in node.signals:
363                 self._write_signal(signal)
364
365     def _write_boxed(self, boxed):
366         attrs = [('c:type', boxed.ctype),
367                  ('glib:name', boxed.name)]
368         if boxed.doc:
369             attrs.append(('doc', boxed.doc))
370         attrs.extend(self._boxed_attrs(boxed))
371         with self.tagcontext('glib:boxed', attrs):
372             self._write_attributes(boxed)
373             for method in boxed.constructors:
374                 self._write_constructor(method)
375             for method in boxed.methods:
376                 self._write_method(method)
377
378     def _write_property(self, prop):
379         attrs = [('name', prop.name)]
380         self._append_version(prop, attrs)
381         self._append_deprecated(prop, attrs)
382         # Properties are assumed to be readable (see also generate.c)
383         if not prop.readable:
384             attrs.append(('readable', '0'))
385         if prop.writable:
386             attrs.append(('writable', '1'))
387         if prop.construct:
388             attrs.append(('construct', '1'))
389         if prop.construct_only:
390             attrs.append(('construct-only', '1'))
391         if prop.doc:
392             attrs.append(('doc', prop.doc))
393         with self.tagcontext('property', attrs):
394             self._write_attributes(prop)
395             self._write_type(prop.type)
396
397     def _write_vfunc(self, vf):
398         attrs = []
399         if vf.invoker:
400             attrs.append(('invoker', vf.invoker))
401         self._write_callable(vf, 'virtual-method', attrs)
402
403     def _write_callback(self, callback):
404         attrs = [('c:type', callback.ctype)]
405         self._write_callable(callback, 'callback', attrs)
406
407     def _boxed_attrs(self, boxed):
408         return [('glib:type-name', boxed.type_name),
409                 ('glib:get-type', boxed.get_type)]
410
411     def _write_record(self, record, extra_attrs=[]):
412         attrs = list(extra_attrs)
413         if record.name is not None:
414             attrs.append(('name', record.name))
415         if record.symbol is not None: # the record might be anonymous
416             attrs.append(('c:type', record.symbol))
417         if record.disguised:
418             attrs.append(('disguised', '1'))
419         if isinstance(record, GLibRecord):
420             if record.is_gtype_struct_for:
421                 attrs.append(('glib:is-gtype-struct-for',
422                               record.is_gtype_struct_for))
423         if record.doc:
424             attrs.append(('doc', record.doc))
425         self._append_version(record, attrs)
426         self._append_deprecated(record, attrs)
427         if isinstance(record, GLibBoxed):
428             attrs.extend(self._boxed_attrs(record))
429         with self.tagcontext('record', attrs):
430             self._write_attributes(record)
431             if record.fields:
432                 for field in record.fields:
433                     self._write_field(field)
434             for method in record.constructors:
435                 self._write_constructor(method)
436             for method in record.methods:
437                 self._write_method(method)
438
439     def _write_union(self, union):
440         attrs = []
441         if union.name is not None:
442             attrs.append(('name', union.name))
443         if union.symbol is not None: # the union might be anonymous
444             attrs.append(('c:type', union.symbol))
445         if union.doc:
446             attrs.append(('doc', union.doc))
447         self._append_version(union, attrs)
448         self._append_deprecated(union, attrs)
449         if isinstance(union, GLibBoxed):
450             attrs.extend(self._boxed_attrs(union))
451         with self.tagcontext('union', attrs):
452             self._write_attributes(union)
453             if union.fields:
454                 for field in union.fields:
455                     self._write_field(field)
456             for method in union.constructors:
457                 self._write_constructor(method)
458             for method in union.methods:
459                 self._write_method(method)
460
461     def _write_field(self, field):
462         if isinstance(field, Function):
463             self._write_method(field)
464             return
465
466         if isinstance(field, Callback):
467             attrs = [('name', field.name)]
468             with self.tagcontext('field', attrs):
469                 self._write_attributes(field)
470                 self._write_callback(field)
471         elif isinstance(field, Struct):
472             self._write_record(field)
473         elif isinstance(field, Union):
474             self._write_union(field)
475         else:
476             attrs = [('name', field.name)]
477             # Fields are assumed to be read-only
478             # (see also girparser.c and generate.c)
479             if not field.readable:
480                 attrs.append(('readable', '0'))
481             if field.writable:
482                 attrs.append(('writable', '1'))
483             if field.bits:
484                 attrs.append(('bits', str(field.bits)))
485             with self.tagcontext('field', attrs):
486                 self._write_attributes(field)
487                 self._write_type(field.type)
488
489     def _write_signal(self, signal):
490         attrs = [('name', signal.name)]
491         if signal.doc:
492             attrs.append(('doc', signal.doc))
493         self._append_version(signal, attrs)
494         self._append_deprecated(signal, attrs)
495         with self.tagcontext('glib:signal', attrs):
496             self._write_attributes(signal)
497             self._write_return_type(signal.retval)
498             self._write_parameters(signal.parameters)