d4989135dcf181aa492db216799576eab4e6a8ab
[platform/upstream/gobject-introspection.git] / giscanner / annotationparser.py
1 # -*- Mode: Python -*-
2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008-2010 Johan Dahlin
4 # Copyright (C) 2012 Dieter Verfaillie <dieterv@optionexplicit.be>
5 #
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License
8 # as published by the Free Software Foundation; either version 2
9 # of the License, or (at your option) any later version.
10 #
11 # This program 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
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20 #
21
22
23 # AnnotationParser - extract annotations from GTK-Doc comment blocks
24
25
26 import re
27
28 from . import message
29 from .annotationpatterns import (COMMENT_START_RE, COMMENT_END_RE,
30                                  COMMENT_ASTERISK_RE, EMPTY_LINE_RE,
31                                  SECTION_RE, SYMBOL_RE, PROPERTY_RE, SIGNAL_RE,
32                                  PARAMETER_RE, DESCRIPTION_TAG_RE, TAG_RE,
33                                  MULTILINE_ANNOTATION_CONTINUATION_RE)
34 from .odict import odict
35
36
37 # GTK-Doc comment block parts
38 PART_IDENTIFIER = 'identifier'
39 PART_PARAMETERS = 'parameters'
40 PART_DESCRIPTION = 'description'
41 PART_TAGS = 'tags'
42
43 # Identifiers
44 IDENTIFIER_SECTION = 'section'
45 IDENTIFIER_SYMBOL = 'symbol'
46 IDENTIFIER_PROPERTY = 'property'
47 IDENTIFIER_SIGNAL = 'signal'
48
49 # Tags - annotations applied to comment blocks
50 TAG_VFUNC = 'virtual'
51 TAG_SINCE = 'since'
52 TAG_STABILITY = 'stability'
53 TAG_DEPRECATED = 'deprecated'
54 TAG_RETURNS = 'returns'
55 TAG_RETURNVALUE = 'return value'
56 TAG_ATTRIBUTES = 'attributes'
57 TAG_RENAME_TO = 'rename to'
58 TAG_TYPE = 'type'
59 TAG_UNREF_FUNC = 'unref func'
60 TAG_REF_FUNC = 'ref func'
61 TAG_SET_VALUE_FUNC = 'set value func'
62 TAG_GET_VALUE_FUNC = 'get value func'
63 TAG_TRANSFER = 'transfer'
64 TAG_VALUE = 'value'
65 _ALL_TAGS = [TAG_VFUNC,
66              TAG_SINCE,
67              TAG_STABILITY,
68              TAG_DEPRECATED,
69              TAG_RETURNS,
70              TAG_RETURNVALUE,
71              TAG_ATTRIBUTES,
72              TAG_RENAME_TO,
73              TAG_TYPE,
74              TAG_UNREF_FUNC,
75              TAG_REF_FUNC,
76              TAG_SET_VALUE_FUNC,
77              TAG_GET_VALUE_FUNC,
78              TAG_TRANSFER,
79              TAG_VALUE]
80
81 # Options - annotations for parameters and return values
82 OPT_ALLOW_NONE = 'allow-none'
83 OPT_ARRAY = 'array'
84 OPT_ATTRIBUTE = 'attribute'
85 OPT_CLOSURE = 'closure'
86 OPT_DESTROY = 'destroy'
87 OPT_ELEMENT_TYPE = 'element-type'
88 OPT_FOREIGN = 'foreign'
89 OPT_IN = 'in'
90 OPT_INOUT = 'inout'
91 OPT_INOUT_ALT = 'in-out'
92 OPT_OUT = 'out'
93 OPT_SCOPE = 'scope'
94 OPT_TRANSFER = 'transfer'
95 OPT_TYPE = 'type'
96 OPT_SKIP = 'skip'
97 OPT_CONSTRUCTOR = 'constructor'
98 OPT_METHOD = 'method'
99
100 ALL_OPTIONS = [
101     OPT_ALLOW_NONE,
102     OPT_ARRAY,
103     OPT_ATTRIBUTE,
104     OPT_CLOSURE,
105     OPT_DESTROY,
106     OPT_ELEMENT_TYPE,
107     OPT_FOREIGN,
108     OPT_IN,
109     OPT_INOUT,
110     OPT_INOUT_ALT,
111     OPT_OUT,
112     OPT_SCOPE,
113     OPT_TRANSFER,
114     OPT_TYPE,
115     OPT_SKIP,
116     OPT_CONSTRUCTOR,
117     OPT_METHOD]
118
119 # Array options - array specific annotations
120 OPT_ARRAY_FIXED_SIZE = 'fixed-size'
121 OPT_ARRAY_LENGTH = 'length'
122 OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
123
124 # Out options
125 OPT_OUT_CALLER_ALLOCATES = 'caller-allocates'
126 OPT_OUT_CALLEE_ALLOCATES = 'callee-allocates'
127
128 # Scope options
129 OPT_SCOPE_ASYNC = 'async'
130 OPT_SCOPE_CALL = 'call'
131 OPT_SCOPE_NOTIFIED = 'notified'
132
133 # Transfer options
134 OPT_TRANSFER_NONE = 'none'
135 OPT_TRANSFER_CONTAINER = 'container'
136 OPT_TRANSFER_FULL = 'full'
137 OPT_TRANSFER_FLOATING = 'floating'
138
139
140 class DocBlock(object):
141
142     def __init__(self, name):
143         self.name = name
144         self.options = DocOptions()
145         self.value = None
146         self.tags = odict()
147         self.comment = None
148         self.params = odict()
149         self.position = None
150
151     def __cmp__(self, other):
152         return cmp(self.name, other.name)
153
154     def __repr__(self):
155         return '<DocBlock %r %r>' % (self.name, self.options)
156
157     def set_position(self, position):
158         self.position = position
159         self.options.position = position
160
161     def get_tag(self, name):
162         return self.tags.get(name)
163
164     def get_param(self, name):
165         return self.params.get(name)
166
167     def to_gtk_doc(self):
168         options = ''
169         if self.options:
170             options += ' '
171             options += ' '.join('(%s)' % o for o in self.options)
172         lines = [self.name]
173         if 'SECTION' not in self.name:
174             lines[0] += ':'
175         lines[0] += options
176         for param in self.params.values():
177             lines.append(param.to_gtk_doc_param())
178         lines.append('')
179         for l in self.comment.split('\n'):
180             lines.append(l)
181         if self.tags:
182             lines.append('')
183             for tag in self.tags.values():
184                 lines.append(tag.to_gtk_doc_tag())
185
186         comment = ''
187         comment += '/**\n'
188         for line in lines:
189             line = line.rstrip()
190             if line:
191                 comment += ' * %s\n' % (line, )
192             else:
193                 comment += ' *\n'
194         comment += ' */\n'
195         return comment
196
197     def validate(self):
198         for param in self.params.values():
199             param.validate()
200
201         for tag in self.tags.values():
202             tag.validate()
203
204
205 class DocTag(object):
206
207     def __init__(self, block, name):
208         self.block = block
209         self.name = name
210         self.options = DocOptions()
211         self.comment = None
212         self.value = ''
213         self.position = None
214
215     def __repr__(self):
216         return '<DocTag %r %r>' % (self.name, self.options)
217
218     def _validate_option(self, name, value, required=False,
219                          n_params=None, choices=None):
220         if required and value is None:
221             message.warn('%s annotation needs a value' % (
222                 name, ), self.position)
223             return
224
225         if n_params is not None:
226             if n_params == 0:
227                 s = 'no value'
228             elif n_params == 1:
229                 s = 'one value'
230             else:
231                 s = '%d values' % (n_params, )
232             if ((n_params > 0 and (value is None or value.length() != n_params)) or
233                 n_params == 0 and value is not None):
234                 if value is None:
235                     length = 0
236                 else:
237                     length = value.length()
238                 message.warn('%s annotation needs %s, not %d' % (
239                     name, s, length), self.position)
240                 return
241
242         if choices is not None:
243             valuestr = value.one()
244             if valuestr not in choices:
245                 message.warn('invalid %s annotation value: %r' % (
246                     name, valuestr, ), self.position)
247                 return
248
249     def _validate_array(self, option, value):
250         if value is None:
251             return
252
253         for name, v in value.all().iteritems():
254             if name in [OPT_ARRAY_ZERO_TERMINATED, OPT_ARRAY_FIXED_SIZE]:
255                 try:
256                     int(v)
257                 except (TypeError, ValueError):
258                     if v is None:
259                         message.warn(
260                             'array option %s needs a value' % (
261                             name, ),
262                             positions=self.position)
263                     else:
264                         message.warn(
265                             'invalid array %s option value %r, '
266                             'must be an integer' % (name, v, ),
267                             positions=self.position)
268             elif name == OPT_ARRAY_LENGTH:
269                 if v is None:
270                     message.warn(
271                         'array option length needs a value',
272                         positions=self.position)
273             else:
274                 message.warn(
275                     'invalid array annotation value: %r' % (
276                     name, ), self.position)
277
278     def _validate_closure(self, option, value):
279         if value is not None and value.length() > 1:
280             message.warn(
281                 'closure takes at most 1 value, %d given' % (
282                 value.length()), self.position)
283
284     def _validate_element_type(self, option, value):
285         self._validate_option(option, value, required=True)
286         if value is None:
287             message.warn(
288                 'element-type takes at least one value, none given',
289                 self.position)
290             return
291         if value.length() > 2:
292             message.warn(
293                 'element-type takes at most 2 values, %d given' % (
294                 value.length()), self.position)
295             return
296
297     def _validate_out(self, option, value):
298         if value is None:
299             return
300         if value.length() > 1:
301             message.warn(
302                 'out annotation takes at most 1 value, %d given' % (
303                 value.length()), self.position)
304             return
305         value_str = value.one()
306         if value_str not in [OPT_OUT_CALLEE_ALLOCATES,
307                              OPT_OUT_CALLER_ALLOCATES]:
308             message.warn("out annotation value is invalid: %r" % (
309                 value_str), self.position)
310             return
311
312     def set_position(self, position):
313         self.position = position
314         self.options.position = position
315
316     def _get_gtk_doc_value(self):
317         def serialize_one(option, value, fmt, fmt2):
318             if value:
319                 if type(value) != str:
320                     value = ' '.join((serialize_one(k, v, '%s=%s', '%s')
321                                       for k, v in value.all().iteritems()))
322                 return fmt % (option, value)
323             else:
324                 return fmt2 % (option, )
325         annotations = []
326         for option, value in self.options.iteritems():
327             annotations.append(
328                 serialize_one(option, value, '(%s %s)', '(%s)'))
329         if annotations:
330             return ' '.join(annotations) + ': '
331         else:
332             return self.value
333
334     def to_gtk_doc_param(self):
335         return '@%s: %s%s' % (self.name, self._get_gtk_doc_value(), self.comment)
336
337     def to_gtk_doc_tag(self):
338         return '%s: %s%s' % (self.name.capitalize(),
339                              self._get_gtk_doc_value(),
340                              self.comment or '')
341
342     def validate(self):
343         if self.name == TAG_ATTRIBUTES:
344             # The 'Attributes:' tag allows free form annotations so the
345             # validation below is most certainly going to fail.
346             return
347
348         for option in self.options:
349             value = self.options[option]
350             if option == OPT_ALLOW_NONE:
351                 self._validate_option(option, value, n_params=0)
352             elif option == OPT_ARRAY:
353                 self._validate_array(option, value)
354             elif option == OPT_ATTRIBUTE:
355                 self._validate_option(option, value, n_params=2)
356             elif option == OPT_CLOSURE:
357                 self._validate_closure(option, value)
358             elif option == OPT_DESTROY:
359                 self._validate_option(option, value, n_params=1)
360             elif option == OPT_ELEMENT_TYPE:
361                 self._validate_element_type(option, value)
362             elif option == OPT_FOREIGN:
363                 self._validate_option(option, value, n_params=0)
364             elif option == OPT_IN:
365                 self._validate_option(option, value, n_params=0)
366             elif option in [OPT_INOUT, OPT_INOUT_ALT]:
367                 self._validate_option(option, value, n_params=0)
368             elif option == OPT_OUT:
369                 self._validate_out(option, value)
370             elif option == OPT_SCOPE:
371                 self._validate_option(
372                     option, value, required=True,
373                     n_params=1,
374                     choices=[OPT_SCOPE_ASYNC,
375                              OPT_SCOPE_CALL,
376                              OPT_SCOPE_NOTIFIED])
377             elif option == OPT_SKIP:
378                 self._validate_option(option, value, n_params=0)
379             elif option == OPT_TRANSFER:
380                 self._validate_option(
381                     option, value, required=True,
382                     n_params=1,
383                     choices=[OPT_TRANSFER_FULL,
384                              OPT_TRANSFER_CONTAINER,
385                              OPT_TRANSFER_NONE,
386                              OPT_TRANSFER_FLOATING])
387             elif option == OPT_TYPE:
388                 self._validate_option(option, value, required=True,
389                                       n_params=1)
390             elif option == OPT_CONSTRUCTOR:
391                 self._validate_option(option, value, n_params=0)
392             elif option == OPT_METHOD:
393                 self._validate_option(option, value, n_params=0)
394             else:
395                 message.warn('invalid annotation option: %s' % (option, ),
396                              self.position)
397
398
399 class DocOptions(object):
400     def __init__(self):
401         self.values = []
402
403     def __repr__(self):
404         return '<DocOptions %r>' % (self.values, )
405
406     def __getitem__(self, item):
407         for key, value in self.values:
408             if key == item:
409                 return value
410         raise KeyError
411
412     def __nonzero__(self):
413         return bool(self.values)
414
415     def __iter__(self):
416         return (k for k, v in self.values)
417
418     def add(self, name, value):
419         self.values.append((name, value))
420
421     def get(self, item, default=None):
422         for key, value in self.values:
423             if key == item:
424                 return value
425         return default
426
427     def getall(self, item):
428         for key, value in self.values:
429             if key == item:
430                 yield value
431
432     def iteritems(self):
433         return iter(self.values)
434
435
436 class DocOption(object):
437
438     def __init__(self, tag, option):
439         self.tag = tag
440         self._array = []
441         self._dict = {}
442         # (annotation option1=value1 option2=value2) etc
443         for p in option.split(' '):
444             if '=' in p:
445                 name, value = p.split('=', 1)
446             else:
447                 name = p
448                 value = None
449             self._dict[name] = value
450             if value is None:
451                 self._array.append(name)
452             else:
453                 self._array.append((name, value))
454
455     def __repr__(self):
456         return '<DocOption %r>' % (self._array, )
457
458     def length(self):
459         return len(self._array)
460
461     def one(self):
462         assert len(self._array) == 1
463         return self._array[0]
464
465     def flat(self):
466         return self._array
467
468     def all(self):
469         return self._dict
470
471
472 class AnnotationParser(object):
473     """
474     GTK-Doc comment block parser.
475
476     Parses GTK-Doc comment blocks into a parse tree built out of :class:`DockBlock`,
477     :class:`DocTag`, :class:`DocOptions` and :class:`DocOption` objects. This
478     parser tries to accept malformed input whenever possible and does not emit
479     syntax errors. However, it does emit warnings at the slightest indication
480     of malformed input when possible. It is usually a good idea to heed these
481     warnings as malformed input is known to result in invalid GTK-Doc output.
482
483     A GTK-Doc comment block can be constructed out of multiple parts that can
484     be combined to write different types of documentation.
485     See `GTK-Doc's documentation`_ to learn more about possible valid combinations.
486     Each part can be further divided into fields which are separated by `:` characters.
487
488     Possible parts and the fields they are constructed from look like the
489     following (optional fields are enclosed in square brackets):
490
491     .. code-block:: c
492         /**
493          * identifier_name [:annotations]
494          * @parameter_name [:annotations] [:description]
495          *
496          * comment_block_description
497          * tag_name [:annotations] [:description]
498          */
499
500     The order in which the different parts have to be specified is important::
501
502         - There has to be exactly 1 `identifier` part on the first line of the
503           comment block which consists of:
504               * an `identifier_name` field
505               * an optional `annotations` field
506         - Followed by 0 or more `parameters` parts, each consisting of:
507               * a `parameter_name` field
508               * an optional `annotations` field
509               * an optional `description` field
510         - Followed by at least 1 empty line signaling the beginning of
511           the `comment_block_description` part
512         - Followed by an optional `comment block description` part.
513         - Followed by 0 or more `tag` parts, each consisting of:
514               * a `tag_name` field
515               * an optional `annotations` field
516               * an optional `description` field
517
518     Additionally, the following restrictions are in effect::
519
520         - Parts can optionally be separated by an empty line, except between
521           the `parameter` parts and the `comment block description` part where
522           an empty line is required (see above).
523         - Parts and fields cannot span multiple lines, except for
524           `parameter descriptions`, `tag descriptions` and the
525           `comment_block_description` fields.
526         - `parameter descriptions` fields can not span multiple paragraphs.
527         - `tag descriptions` and `comment block description` fields can
528           span multiple paragraphs.
529
530     .. NOTE:: :class:`AnnotationParser` functionality is heavily based on gtkdoc-mkdb's
531         `ScanSourceFile()`_ function and is currently in sync with GTK-Doc
532         commit `b41641b`_.
533
534     .. _GTK-Doc's documentation:
535             http://developer.gnome.org/gtk-doc-manual/1.18/documenting.html.en
536     .. _ScanSourceFile():
537             http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
538     .. _b41641b: b41641bd75f870afff7561ceed8a08456da57565
539     """
540
541     def parse(self, comments):
542         """
543         Parses multiple GTK-Doc comment blocks.
544
545         :param comments: a list of (comment, filename, lineno) tuples
546         :returns: a dictionary mapping identifier names to :class:`DocBlock` objects
547         """
548
549         comment_blocks = {}
550
551         for comment in comments:
552             comment_block = self.parse_comment_block(comment)
553
554             if comment_block is not None:
555                 # Note: previous versions of this parser did not check
556                 # if an identifier was already stored in comment_blocks,
557                 # so when multiple comment blocks where encountered documenting
558                 # the same identifier the last one seen "wins".
559                 # Keep this behavior for backwards compatibility, but
560                 # emit a warning.
561                 if comment_block.name in comment_blocks:
562                     message.warn("multiple comment blocks documenting '%s:' identifier." %
563                                  (comment_block.name),
564                                  comment_block.position)
565
566                 comment_blocks[comment_block.name] = comment_block
567
568         return comment_blocks
569
570     def parse_comment_block(self, comment):
571         """
572         Parses a single GTK-Doc comment block.
573
574         :param comment: a (comment, filename, lineno) tuple
575         :returns: a :class:`DocBlock` object or ``None``
576         """
577
578         comment, filename, lineno = comment
579
580         # Assign line numbers to each line of the comment block,
581         # which will later be used as the offset to calculate the
582         # real line number in the source file
583         comment_lines = list(enumerate(comment.split('\n')))
584
585         # Check for the start the comment block.
586         if COMMENT_START_RE.search(comment_lines[0][1]):
587             del comment_lines[0]
588         else:
589             # Not a GTK-Doc comment block.
590             return None
591
592         # Check for the end the comment block.
593         if COMMENT_END_RE.search(comment_lines[-1][1]):
594             del comment_lines[-1]
595
596         # If we get this far, we are inside a GTK-Doc comment block.
597         return self._parse_comment_block(comment_lines, filename, lineno)
598
599     def _parse_comment_block(self, comment_lines, filename, lineno):
600         """
601         Parses a single GTK-Doc comment block already stripped from its
602         comment start (/**) and comment end (*/) marker lines.
603
604         :param comment_lines: list of (line_offset, line) tuples representing a
605                               GTK-Doc comment block already stripped from it's
606                               start (/**) and end (*/) marker lines
607         :param filename: source file name where the comment block originated from
608         :param lineno:  line in the source file where the comment block starts
609         :returns: a :class:`DocBlock` object or ``None``
610
611         .. NOTE:: If you are tempted to refactor this method and split it
612             further up (for example into _parse_identifier(), _parse_parameters(),
613             _parse_description(), _parse_tags() methods) then please resist the
614             urge. It is considered important that this method should be more or
615             less easily comparable with gtkdoc-mkdb's `ScanSourceFile()`_ function.
616
617             The different parsing steps are marked with a comment surrounded
618             by `#` characters in an attempt to make it clear what is going on.
619
620         .. _ScanSourceFile():
621                 http://git.gnome.org/browse/gtk-doc/tree/gtkdoc-mkdb.in#n3722
622         """
623         comment_block = None
624         in_part = None
625         identifier = None
626         current_param = None
627         current_tag = None
628         returns_seen = False
629
630         for line_offset, line in comment_lines:
631             position = message.Position(filename, line_offset + lineno)
632
633             # Store the original line (without \n) and column offset
634             # so we can generate meaningful warnings later on.
635             original_line = line
636             column_offset = 0
637
638             # Get rid of ' * ' at start of the line.
639             result = COMMENT_ASTERISK_RE.match(line)
640             if result:
641                 column_offset = result.end(0)
642                 line = line[result.end(0):]
643
644             ####################################################################
645             # Check for GTK-Doc comment block identifier.
646             ####################################################################
647             if not comment_block:
648                 # The correct identifier name would have the colon at the end
649                 # but maintransformer.py does not expect us to do that. So
650                 # make sure to compute an identifier_name without the colon and
651                 # a real_identifier_name with the colon.
652
653                 if not identifier:
654                     result = SECTION_RE.search(line)
655                     if result:
656                         identifier = IDENTIFIER_SECTION
657                         real_identifier_name = 'SECTION:%s' % (result.group('section_name'))
658                         identifier_name = real_identifier_name
659                         column = result.start('section_name') + column_offset
660
661                 if not identifier:
662                     result = SYMBOL_RE.search(line)
663                     if result:
664                         identifier = IDENTIFIER_SYMBOL
665                         real_identifier_name = '%s:' % (result.group('symbol_name'))
666                         identifier_name = '%s' % (result.group('symbol_name'))
667                         column = result.start('symbol_name') + column_offset
668
669                 if not identifier:
670                     result = PROPERTY_RE.search(line)
671                     if result:
672                         identifier = IDENTIFIER_PROPERTY
673                         real_identifier_name = '%s:%s:' % (result.group('class_name'),
674                                                            result.group('property_name'))
675                         identifier_name = '%s:%s' % (result.group('class_name'),
676                                                      result.group('property_name'))
677                         column = result.start('property_name') + column_offset
678
679                 if not identifier:
680                     result = SIGNAL_RE.search(line)
681                     if result:
682                         identifier = IDENTIFIER_SIGNAL
683                         real_identifier_name = '%s::%s:' % (result.group('class_name'),
684                                                             result.group('signal_name'))
685                         identifier_name = '%s::%s' % (result.group('class_name'),
686                                                       result.group('signal_name'))
687                         column = result.start('signal_name') + column_offset
688
689                 if identifier:
690                     in_part = PART_IDENTIFIER
691
692                     comment_block = DocBlock(identifier_name)
693                     comment_block.set_position(position)
694
695                     if 'colon' in result.groupdict() and result.group('colon') != ':':
696                         colon_start = result.start('colon')
697                         colon_column = column_offset + colon_start
698                         marker = ' '*colon_column + '^'
699                         message.warn("missing ':' at column %s:\n%s\n%s" %
700                                      (colon_column + 1, original_line, marker),
701                                      position)
702
703                     if 'annotations' in result.groupdict():
704                         comment_block.options = self.parse_options(comment_block,
705                                                                    result.group('annotations'))
706
707                     continue
708                 else:
709                     # If we get here, the identifier was not recognized, so
710                     # ignore the rest of the block just like the old annotation
711                     # parser did. Doing this is a bit more strict than
712                     # gtkdoc-mkdb (which continues to search for the identifier
713                     # until either it is found or the end of the block is
714                     # reached). In practice, however, ignoring the block is the
715                     # right thing to do because sooner or later some long
716                     # descriptions will contain something matching an identifier
717                     # pattern by accident.
718                     marker = ' '*column_offset + '^'
719                     message.warn('ignoring unrecognized GTK-Doc comment block, identifier not '
720                                  'found:\n%s\n%s' % (original_line, marker),
721                                  position)
722
723                     return None
724
725             ####################################################################
726             # Check for comment block parameters.
727             ####################################################################
728             result = PARAMETER_RE.search(line)
729             if result:
730                 param_name = result.group('parameter_name')
731                 param_annotations = result.group('annotations')
732                 param_description = result.group('description')
733
734                 if in_part == PART_IDENTIFIER:
735                     in_part = PART_PARAMETERS
736
737                 if in_part != PART_PARAMETERS:
738                     column = result.start('parameter_name') + column_offset
739                     marker = ' '*column + '^'
740                     message.warn("'@%s' parameter unexpected at this location:\n%s\n%s" %
741                                  (param_name, original_line, marker),
742                                  position)
743
744                 # Old style GTK-Doc allowed return values to be specified as
745                 # parameters instead of tags.
746                 if param_name.lower() == TAG_RETURNS:
747                     param_name = TAG_RETURNS
748
749                     if not returns_seen:
750                         returns_seen = True
751                     else:
752                         message.warn("encountered multiple 'Returns' parameters or tags for "
753                                      "'%s'." % (comment_block.name),
754                                      position)
755                 elif param_name in comment_block.params.keys():
756                     column = result.start('parameter_name') + column_offset
757                     marker = ' '*column + '^'
758                     message.warn("multiple '@%s' parameters for identifier '%s':\n%s\n%s" %
759                                  (param_name, comment_block.name, original_line, marker),
760                                  position)
761
762                 tag = DocTag(comment_block, param_name)
763                 tag.set_position(position)
764                 tag.comment = param_description
765                 if param_annotations:
766                     tag.options = self.parse_options(tag, param_annotations)
767                 if param_name == TAG_RETURNS:
768                     comment_block.tags[param_name] = tag
769                 else:
770                     comment_block.params[param_name] = tag
771                 current_param = tag
772                 continue
773
774             ####################################################################
775             # Check for comment block description.
776             #
777             # When we are parsing comment block parameters or the comment block
778             # identifier (when there are no parameters) and encounter an empty
779             # line, we must be parsing the comment block description.
780             ####################################################################
781             if (EMPTY_LINE_RE.search(line)
782             and in_part in [PART_IDENTIFIER, PART_PARAMETERS]):
783                 in_part = PART_DESCRIPTION
784                 continue
785
786             ####################################################################
787             # Check for GTK-Doc comment block tags.
788             ####################################################################
789             result = TAG_RE.search(line)
790             if result:
791                 tag_name = result.group('tag_name')
792                 tag_annotations = result.group('annotations')
793                 tag_description = result.group('description')
794
795                 if in_part == PART_DESCRIPTION:
796                     in_part = PART_TAGS
797
798                 if in_part != PART_TAGS:
799                     column = result.start('tag_name') + column_offset
800                     marker = ' '*column + '^'
801                     message.warn("'%s:' tag unexpected at this location:\n%s\n%s" %
802                                  (tag_name, original_line, marker),
803                                  position)
804
805                 if tag_name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
806                     if not returns_seen:
807                         returns_seen = True
808                     else:
809                         message.warn("encountered multiple 'Returns' parameters or tags for "
810                                      "'%s'." % (comment_block.name),
811                                      position)
812
813                     tag = DocTag(comment_block, TAG_RETURNS)
814                     tag.position = position
815                     tag.comment = tag_description
816                     if tag_annotations:
817                         tag.options = self.parse_options(tag, tag_annotations)
818                     comment_block.tags[TAG_RETURNS] = tag
819                     current_tag = tag
820                     continue
821                 else:
822                     if tag_name.lower() in comment_block.tags.keys():
823                         column = result.start('tag_name') + column_offset
824                         marker = ' '*column + '^'
825                         message.warn("multiple '%s:' tags for identifier '%s':\n%s\n%s" %
826                                      (tag_name, comment_block.name, original_line, marker),
827                                      position)
828
829                     tag = DocTag(comment_block, tag_name.lower())
830                     tag.position = position
831                     tag.value = tag_description
832                     if tag_annotations:
833                         if tag_name.lower() == TAG_ATTRIBUTES:
834                             tag.options = self.parse_options(tag, tag_annotations)
835                         else:
836                             message.warn("annotations not supported for tag '%s:'." %
837                                          (tag_name),
838                                          position)
839                     comment_block.tags[tag_name.lower()] = tag
840                     current_tag = tag
841                     continue
842
843             ####################################################################
844             # If we get here, we must be in the middle of a multiline
845             # comment block, parameter or tag description.
846             ####################################################################
847             if in_part in [PART_IDENTIFIER, PART_DESCRIPTION]:
848                 if not comment_block.comment:
849                     # Backwards compatibility with old style GTK-Doc
850                     # comment blocks where Description used to be a comment
851                     # block tag. Simply get rid of 'Description:'.
852                     line = re.sub(DESCRIPTION_TAG_RE, '', line)
853                     comment_block.comment = line
854                 else:
855                     comment_block.comment += '\n' + line
856                 continue
857             elif in_part == PART_PARAMETERS:
858                 self._validate_multiline_annotation_continuation(line, original_line,
859                                                                  column_offset, position)
860
861                 # Append to parameter description.
862                 current_param.comment += ' ' + line.strip()
863             elif in_part == PART_TAGS:
864                 self._validate_multiline_annotation_continuation(line, original_line,
865                                                                  column_offset, position)
866
867                 # Append to tag description.
868                 if current_tag.name.lower() in [TAG_RETURNS, TAG_RETURNVALUE]:
869                     current_tag.comment += ' ' + line.strip()
870                 else:
871                     current_tag.value += ' ' + line.strip()
872
873         ########################################################################
874         # Finished parsing this comment block.
875         ########################################################################
876         # We have picked up a couple of \n characters that where not
877         # intended. Strip those.
878         if comment_block.comment:
879             comment_block.comment = comment_block.comment.strip()
880         else:
881             comment_block.comment = ''
882
883         for tag in comment_block.tags.itervalues():
884             self._clean_comment_block_part(tag)
885
886         for param in comment_block.params.itervalues():
887             self._clean_comment_block_part(param)
888
889         # Validate and store block.
890         comment_block.validate()
891         return comment_block
892
893     def _clean_comment_block_part(self, part):
894         if part.comment:
895             part.comment = part.comment.strip()
896         else:
897             part.comment = None
898
899         if part.value:
900             part.value = part.value.strip()
901         else:
902             part.value = ''
903
904     def _validate_multiline_annotation_continuation(self, line, original_line,
905                                                           column_offset, position):
906         '''
907         Validate parameters and tags (except the first line) and generate
908         warnings about invalid annotations spanning multiple lines.
909
910         :param line: line to validate, stripped from ' * ' at start of the line.
911         :param original_line: original line to validate (used in warning messages)
912         :param column_offset: column width of ' * ' at the time it was stripped from `line`
913         :param position: position of `line` in the source file
914         '''
915
916         result = MULTILINE_ANNOTATION_CONTINUATION_RE.search(line)
917         if result:
918             line = result.group('description')
919             column = result.start('annotations') + column_offset
920             marker = ' '*column + '^'
921             message.warn('ignoring invalid multiline annotation continuation:\n'
922                          '%s\n%s' % (original_line, marker),
923                          position)
924
925     @classmethod
926     def parse_options(cls, tag, value):
927         # (annotation)
928         # (annotation opt1 opt2 ...)
929         # (annotation opt1=value1 opt2=value2 ...)
930         opened = -1
931         options = DocOptions()
932         options.position = tag.position
933
934         for i, c in enumerate(value):
935             if c == '(' and opened == -1:
936                 opened = i+1
937             if c == ')' and opened != -1:
938                 segment = value[opened:i]
939                 parts = segment.split(' ', 1)
940                 if len(parts) == 2:
941                     name, option = parts
942                 elif len(parts) == 1:
943                     name = parts[0]
944                     option = None
945                 else:
946                     raise AssertionError
947                 if option is not None:
948                     option = DocOption(tag, option)
949                 options.add(name, option)
950                 opened = -1
951
952         return options