Organize annotation options/tags by using constants,
[gnome.gobject-introspection] / giscanner / annotationparser.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 # AnnotationParser - parses gtk-doc annotations
22
23 from .ast import (Array, Callback, Class, Enum, Field, Function, Interface,
24                   List, Map, Parameter, Record, Return, Type, Union, Varargs,
25                   default_array_types,
26                   BASIC_GIR_TYPES,
27                   PARAM_DIRECTION_INOUT,
28                   PARAM_DIRECTION_IN,
29                   PARAM_DIRECTION_OUT,
30                   PARAM_TRANSFER_NONE,
31                   PARAM_TRANSFER_CONTAINER,
32                   PARAM_TRANSFER_FULL,
33                   TYPE_ANY, TYPE_NONE)
34 from .odict import odict
35 from .glibast import GLibBoxed
36
37 # All gtk-doc comments needs to start with this:
38 _COMMENT_HEADER = '*\n '
39
40 # Tags - annotations applyed to comment blocks
41 TAG_SINCE = 'Since'
42 TAG_DEPRECATED = 'Deprecated'
43 TAG_RETURNS = 'Returns'
44 TAG_RETURNS_ALT = 'Return value'
45
46 # Options - annotations for parameters and return values
47 OPT_ALLOW_NONE = 'allow-none'
48 OPT_ARRAY = 'array'
49 OPT_ELEMENT_TYPE = 'element-type'
50 OPT_IN = 'in'
51 OPT_INOUT = 'inout'
52 OPT_INOUT_ALT = 'in-out'
53 OPT_OUT = 'out'
54 OPT_SCOPE = 'scope'
55 OPT_TRANSFER = 'transfer'
56 OPT_TYPE = 'type'
57
58 # Array options - array specific annotations
59 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
60 OPT_ARRAY_LENGTH = 'length'
61 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
62
63
64 class InvalidAnnotationError(Exception):
65     pass
66
67
68 class DocBlock(object):
69
70     def __init__(self, name):
71         self.name = name
72         self.value = None
73         self.tags = odict()
74
75     def __repr__(self):
76         return '<DocBlock %r>' % (self.name, )
77
78     def get(self, name):
79         if name == TAG_RETURNS:
80             value = self.tags.get(name)
81             if value is None:
82                 return self.tags.get(TAG_RETURNS_ALT)
83             else:
84                 return value
85         else:
86             return self.tags.get(name)
87
88
89 class DocTag(object):
90
91     def __init__(self, name):
92         self.name = name
93         self.options = []
94
95
96 class Option(object):
97
98     def __init__(self, option):
99         self._array = []
100         self._dict = {}
101         for p in option.split(' '):
102             if '=' in p:
103                 name, value = p.split('=', 1)
104             else:
105                 name = p
106                 value = None
107             self._dict[name] = value
108             if value is None:
109                 self._array.append(name)
110             else:
111                 self._array.append((name, value))
112
113     def __repr__(self):
114         return '<Option %r>' % (self._array, )
115
116     def one(self):
117         assert len(self._array) == 1
118         return self._array[0]
119
120     def flat(self):
121         return self._array
122
123     def all(self):
124         return self._dict
125
126
127 class AnnotationParser(object):
128
129     def __init__(self, namespace, source_scanner, transformer):
130         self._blocks = {}
131         self._namespace = namespace
132         self._transformer = transformer
133         for comment in source_scanner.get_comments():
134             self._parse_comment(comment)
135
136     def parse(self):
137         aa = AnnotationApplier(self._blocks, self._transformer)
138         aa.parse(self._namespace)
139
140     def _parse_comment(self, comment):
141         comment = comment.lstrip()
142         if not comment.startswith(_COMMENT_HEADER):
143             return
144         comment = comment[len(_COMMENT_HEADER):]
145         comment = comment.strip()
146         if not comment.startswith('* '):
147             return
148         comment = comment[2:]
149
150         pos = comment.index('\n ')
151
152         block_name = comment[:pos]
153         block_name = block_name.strip()
154         if not block_name.endswith(':'):
155             return
156         block = DocBlock(block_name[:-1])
157         content = comment[pos+1:]
158         for line in content.split('\n'):
159             line = line.lstrip()
160             line = line[2:].strip() # Skip ' *'
161             if not line:
162                 continue
163
164             if line.startswith('@'):
165                 line = line[1:]
166             elif not ': ' in line:
167                 continue
168             tag = self._parse_tag(line)
169             block.tags[tag.name] = tag
170
171         self._blocks[block.name] = block
172
173     def _parse_tag(self, value):
174         # Tag: bar
175         # Tag: bar opt1 opt2
176         parts = value.split(': ', 1)
177         if len(parts) == 1:
178             tag_name = parts[0]
179             options = ''
180         else:
181             tag_name, options = parts
182         tag = DocTag(tag_name)
183         tag.value = options
184         tag.options = self._parse_options(options)
185         return tag
186
187     def _parse_options(self, value):
188         # (foo)
189         # (bar opt1 opt2...)
190         opened = -1
191         options = {}
192         for i, c in enumerate(value):
193             if c == '(' and opened == -1:
194                 opened = i+1
195             if c == ')' and opened != -1:
196                 segment = value[opened:i]
197                 parts = segment.split(' ', 1)
198                 if len(parts) == 2:
199                     name, option = parts
200                 elif len(parts) == 1:
201                     name = parts[0]
202                     option = None
203                 else:
204                     raise AssertionError
205                 if option is not None:
206                     option = Option(option)
207                 options[name] = option
208                 opened = -1
209         return options
210
211
212 class AnnotationApplier(object):
213
214     def __init__(self, blocks, transformer):
215         self._blocks = blocks
216         self._transformer = transformer
217
218     def _get_tag(self, block, tag_name):
219         if block is None:
220             return None
221
222         return block.get(tag_name)
223
224     def parse(self, namespace):
225         for node in namespace.nodes:
226             self._parse_node(node)
227
228     # Boring parsing boilerplate.
229
230     def _parse_node(self, node):
231         if isinstance(node, Function):
232             self._parse_function(node)
233         elif isinstance(node, Enum):
234             self._parse_enum(node)
235         elif isinstance(node, Class):
236             self._parse_class(node)
237         elif isinstance(node, Interface):
238             self._parse_interface(node)
239         elif isinstance(node, Callback):
240             self._parse_callback(node)
241         elif isinstance(node, Record):
242             self._parse_record(node)
243         elif isinstance(node, Union):
244             self._parse_union(node)
245         elif isinstance(node, GLibBoxed):
246             self._parse_boxed(node)
247
248     def _parse_class(self, class_):
249         block = self._blocks.get(class_.name)
250         self._parse_version(class_, block)
251         self._parse_constructors(class_.constructors)
252         self._parse_methods(class_.methods)
253         self._parse_methods(class_.static_methods)
254         self._parse_properties(class_, class_.properties)
255         self._parse_signals(class_, class_.signals)
256         self._parse_fields(class_, class_.fields)
257
258     def _parse_interface(self, interface):
259         block = self._blocks.get(interface.name)
260         self._parse_version(interface, block)
261         self._parse_methods(interface.methods)
262         self._parse_properties(interface, interface.properties)
263         self._parse_signals(interface, interface.signals)
264         self._parse_fields(interface, interface.fields)
265
266     def _parse_record(self, record):
267         block = self._blocks.get(record.symbol)
268         self._parse_version(record, block)
269         self._parse_constructors(record.constructors)
270         self._parse_fields(record, record.fields)
271         if isinstance(record, GLibBoxed):
272             self._parse_methods(record.methods)
273
274     def _parse_boxed(self, boxed):
275         block = self._blocks.get(boxed.name)
276         self._parse_version(boxed, block)
277         self._parse_constructors(boxed.constructors)
278         self._parse_methods(boxed.methods)
279
280     def _parse_union(self, union):
281         block = self._blocks.get(union.name)
282         self._parse_fields(union, union.fields)
283         self._parse_constructors(union.constructors)
284         if isinstance(union, GLibBoxed):
285             self._parse_methods(union.methods)
286
287     def _parse_enum(self, enum):
288         block = self._blocks.get(enum.symbol)
289         self._parse_version(enum, block)
290
291     def _parse_constructors(self, constructors):
292         for ctor in constructors:
293             self._parse_function(ctor)
294
295     def _parse_fields(self, parent, fields):
296         for field in fields:
297             self._parse_field(parent, field)
298
299     def _parse_properties(self, parent, properties):
300         for prop in properties:
301             self._parse_property(parent, prop)
302
303     def _parse_methods(self, methods):
304         for method in methods:
305             self._parse_function(method)
306
307     def _parse_signals(self, parent, signals):
308         for signal in signals:
309             self._parse_signal(parent, signal)
310
311     def _parse_property(self, parent, prop):
312         block = self._blocks.get('%s:%s' % (parent.type_name, prop.name))
313         self._parse_version(prop, block)
314         self._parse_deprecated(prop, block)
315
316     def _parse_callback(self, callback):
317         block = self._blocks.get(callback.ctype)
318         self._parse_version(callback, block)
319         self._parse_params(callback, callback.parameters, block)
320         self._parse_return(callback, callback.retval, block)
321
322     def _parse_function(self, func):
323         block = self._blocks.get(func.symbol)
324         self._parse_version(func, block)
325         self._parse_deprecated(func, block)
326         self._parse_params(func, func.parameters, block)
327         self._parse_return(func, func.retval, block)
328
329     def _parse_signal(self, parent, signal):
330         block = self._blocks.get('%s::%s' % (parent.type_name, signal.name))
331         self._parse_version(signal, block)
332         self._parse_deprecated(signal, block)
333         # We're only attempting to name the signal parameters if
334         # the number of parameter tags (@foo) is the same or greater
335         # than the number of signal parameters
336         if block and len(block.tags) > len(signal.parameters):
337             names = block.tags.items()
338         else:
339             names = []
340         for i, param in enumerate(signal.parameters):
341             if names:
342                 name, tag = names[i+1]
343                 param.name = name
344                 options = getattr(tag, 'options', {})
345                 param_type = options.get(OPT_TYPE)
346                 if param_type:
347                     param.type.name = param_type.one()
348             else:
349                 tag = None
350             self._parse_param(signal, param, tag)
351         self._parse_return(signal, signal.retval, block)
352
353     def _parse_field(self, parent, field):
354         if isinstance(field, Callback):
355             self._parse_callback(field)
356
357     def _parse_params(self, parent, params, block):
358         for param in params:
359             tag = self._get_tag(block, param.name)
360             self._parse_param(parent, param, tag)
361
362     def _parse_return(self, parent, return_, block):
363         tag = self._get_tag(block, TAG_RETURNS)
364         options = getattr(tag, 'options', {})
365         self._parse_param_ret_common(parent, return_, options)
366
367     def _parse_param(self, parent, param, tag):
368         options = getattr(tag, 'options', {})
369
370         if isinstance(parent, Function):
371             scope = options.get(OPT_SCOPE)
372             if scope:
373                 param.scope = scope.one()
374                 param.transfer = PARAM_TRANSFER_NONE
375         self._parse_param_ret_common(parent, param, options)
376
377     def _parse_param_ret_common(self, parent, node, options):
378         node.direction = self._extract_direction(node, options)
379         container_type = self._extract_container_type(
380             parent, node, options)
381         if container_type is not None:
382             node.type = container_type
383         if node.direction is None:
384             node.direction = self._guess_direction(node)
385         node.transfer = self._extract_transfer(parent, node, options)
386         if OPT_ALLOW_NONE in options:
387             node.allow_none = True
388
389         assert node.transfer is not None
390
391     def _extract_direction(self, node, options):
392         if (OPT_INOUT in options or
393             OPT_INOUT_ALT in options):
394             direction = PARAM_DIRECTION_INOUT
395         elif OPT_OUT in options:
396             direction = PARAM_DIRECTION_OUT
397         elif OPT_IN in options:
398             direction = PARAM_DIRECTION_IN
399         else:
400             direction = node.direction
401         return direction
402
403     def _guess_array(self, node):
404         ctype = node.type.ctype
405         if ctype is None:
406             return False
407         if not ctype.endswith('*'):
408             return False
409         if node.type.canonical in default_array_types:
410             return True
411         return False
412
413     def _extract_container_type(self, parent, node, options):
414         has_element_type = OPT_ELEMENT_TYPE in options
415         has_array = OPT_ARRAY in options
416
417         # FIXME: This is a hack :-(
418         if (not isinstance(node, Field) and
419             (not has_element_type and
420              (node.direction is None
421               or node.direction == PARAM_DIRECTION_IN))):
422             if self._guess_array(node):
423                 has_array = True
424
425         if has_array:
426             container_type = self._parse_array(parent, node, options)
427         elif has_element_type:
428             container_type = self._parse_element_type(parent, node, options)
429         else:
430             container_type = None
431
432         return container_type
433
434     def _parse_array(self, parent, node, options):
435         array_opt = options.get(OPT_ARRAY)
436         if array_opt:
437             array_values = array_opt.all()
438         else:
439             array_values = {}
440
441         element_type = options.get(OPT_ELEMENT_TYPE)
442         if element_type is not None:
443             element_type_name = element_type.one()
444         else:
445             element_type_name = node.type.name
446
447         container_type = Array(node.type.ctype,
448                                element_type_name)
449         if OPT_ARRAY_ZERO_TERMINATED in array_values:
450             container_type.zeroterminated = array_values.get(
451                 OPT_ARRAY_ZERO_TERMINATED) == '1'
452         length = array_values.get(OPT_ARRAY_LENGTH)
453         if length is not None:
454             param_index = parent.get_parameter_index(length)
455             container_type.length_param_index = param_index
456             # For in parameters we're incorrectly deferring
457             # char/unsigned char to utf8 when a length annotation
458             # is specified.
459             if (isinstance(node, Parameter) and
460                 node.type.name == 'utf8' and
461                 self._guess_direction(node) == PARAM_DIRECTION_IN):
462                 # FIXME: unsigned char/guchar should be uint8
463                 container_type.element_type = 'int8'
464         container_type.size = array_values.get(OPT_ARRAY_FIXED_SIZE)
465         return container_type
466
467     def _parse_element_type(self, parent, node, options):
468         element_type_opt = options.get(OPT_ELEMENT_TYPE)
469         element_type = element_type_opt.flat()
470         if node.type.name in ['GLib.List', 'GLib.SList']:
471             assert len(element_type) == 1
472             etype = Type(element_type[0])
473             container_type = List(
474                 node.type.name,
475                 node.type.ctype,
476                 self._transformer.resolve_param_type(etype))
477         elif node.type.name in ['GLib.HashTable']:
478             assert len(element_type) == 2
479             key_type = Type(element_type[0])
480             value_type = Type(element_type[1])
481             container_type = Map(
482                 node.type.name,
483                 node.type.ctype,
484                 self._transformer.resolve_param_type(key_type),
485                 self._transformer.resolve_param_type(value_type))
486         else:
487             print 'FIXME: unhandled element-type container:', node
488         return container_type
489
490     def _extract_transfer(self, parent, node, options):
491         transfer_opt = options.get(OPT_TRANSFER)
492         if transfer_opt is None:
493             transfer = self._guess_transfer(node, options)
494         else:
495             transfer = transfer_opt.one()
496             if transfer is None:
497                 transfer = PARAM_TRANSFER_FULL
498             if transfer not in [PARAM_TRANSFER_NONE,
499                                 PARAM_TRANSFER_CONTAINER,
500                                 PARAM_TRANSFER_FULL]:
501                 raise InvalidAnnotationError(
502                     "transfer for %s of %r is invalid (%r), must be one of "
503                     "none, container, full." % (node, parent.name, transfer))
504         return transfer
505
506     def _parse_version(self, node, block):
507         since_tag = self._get_tag(block, TAG_SINCE)
508         if since_tag is None:
509             return
510         node.version = since_tag.value
511
512     def _parse_deprecated(self, node, block):
513         deprecated_tag = self._get_tag(block, TAG_DEPRECATED)
514         if deprecated_tag is None:
515             return
516         value = deprecated_tag.value
517         if ': ' in value:
518             version, desc = value.split(': ')
519         else:
520             desc = value
521             version = None
522         node.deprecated = desc
523         if version is not None:
524             node.deprecated_version = version
525
526     def _guess_direction(self, node):
527         if node.direction:
528             return node.direction
529         is_pointer = False
530         if node.type.ctype:
531             is_pointer = '*' in node.type.ctype
532
533         if is_pointer and node.type.name in BASIC_GIR_TYPES:
534             return PARAM_DIRECTION_OUT
535
536         return PARAM_DIRECTION_IN
537
538     def _guess_transfer(self, node, options):
539         if node.transfer is not None:
540             return node.transfer
541
542         if isinstance(node.type, Array):
543             return PARAM_TRANSFER_NONE
544         # Anything with 'const' gets none
545         if node.type.is_const:
546             return PARAM_TRANSFER_NONE
547
548         elif node.type.name in [TYPE_NONE, TYPE_ANY]:
549             return PARAM_TRANSFER_NONE
550         elif isinstance(node.type, Varargs):
551             return PARAM_TRANSFER_NONE
552         elif isinstance(node, Parameter):
553             if node.direction in [PARAM_DIRECTION_INOUT,
554                                   PARAM_DIRECTION_OUT]:
555                 return PARAM_TRANSFER_FULL
556             # This one is a hack for compatibility; the transfer
557             # for string parameters really has no defined meaning.
558             elif node.type.canonical == 'utf8':
559                 return PARAM_TRANSFER_FULL
560             else:
561                 return PARAM_TRANSFER_NONE
562         elif isinstance(node, Return):
563             if (node.type.canonical in BASIC_GIR_TYPES or
564                 (node.type.canonical in [TYPE_NONE, TYPE_ANY] and
565                  node.type.is_const)):
566                 return PARAM_TRANSFER_NONE
567             else:
568                 return PARAM_TRANSFER_FULL
569         elif isinstance(node, Field):
570             return PARAM_TRANSFER_NONE
571         else:
572             raise AssertionError(node)