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