2 # GObject-Introspection - a framework for introspecting GObject libraries
3 # Copyright (C) 2008 Johan Dahlin
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.
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.
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
21 # AnnotationParser - parses gtk-doc annotations
23 from .ast import (Array, Callback, Class, Enum, Field, Function, Interface,
24 List, Map, Parameter, Record, Return, Type, Union, Varargs,
27 PARAM_DIRECTION_INOUT,
31 PARAM_TRANSFER_CONTAINER,
34 from .odict import odict
35 from .glibast import GLibBoxed
37 # All gtk-doc comments needs to start with this:
38 _COMMENT_HEADER = '*\n '
40 # Tags - annotations applyed to comment blocks
42 TAG_DEPRECATED = 'Deprecated'
43 TAG_RETURNS = 'Returns'
44 TAG_RETURNS_ALT = 'Return value'
46 # Options - annotations for parameters and return values
47 OPT_ALLOW_NONE = 'allow-none'
49 OPT_ELEMENT_TYPE = 'element-type'
52 OPT_INOUT_ALT = 'in-out'
55 OPT_TRANSFER = 'transfer'
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'
64 class InvalidAnnotationError(Exception):
68 class DocBlock(object):
70 def __init__(self, name):
76 return '<DocBlock %r>' % (self.name, )
79 if name == TAG_RETURNS:
80 value = self.tags.get(name)
82 return self.tags.get(TAG_RETURNS_ALT)
86 return self.tags.get(name)
91 def __init__(self, name):
98 def __init__(self, option):
101 for p in option.split(' '):
103 name, value = p.split('=', 1)
107 self._dict[name] = value
109 self._array.append(name)
111 self._array.append((name, value))
114 return '<Option %r>' % (self._array, )
117 assert len(self._array) == 1
118 return self._array[0]
127 class AnnotationParser(object):
129 def __init__(self, namespace, source_scanner, transformer):
131 self._namespace = namespace
132 self._transformer = transformer
133 for comment in source_scanner.get_comments():
134 self._parse_comment(comment)
137 aa = AnnotationApplier(self._blocks, self._transformer)
138 aa.parse(self._namespace)
140 def _parse_comment(self, comment):
141 comment = comment.lstrip()
142 if not comment.startswith(_COMMENT_HEADER):
144 comment = comment[len(_COMMENT_HEADER):]
145 comment = comment.strip()
146 if not comment.startswith('* '):
148 comment = comment[2:]
150 pos = comment.index('\n ')
152 block_name = comment[:pos]
153 block_name = block_name.strip()
154 if not block_name.endswith(':'):
156 block = DocBlock(block_name[:-1])
157 content = comment[pos+1:]
158 for line in content.split('\n'):
160 line = line[2:].strip() # Skip ' *'
164 if line.startswith('@'):
166 elif not ': ' in line:
168 tag = self._parse_tag(line)
169 block.tags[tag.name] = tag
171 self._blocks[block.name] = block
173 def _parse_tag(self, value):
176 parts = value.split(': ', 1)
181 tag_name, options = parts
182 tag = DocTag(tag_name)
184 tag.options = self._parse_options(options)
187 def _parse_options(self, value):
192 for i, c in enumerate(value):
193 if c == '(' and opened == -1:
195 if c == ')' and opened != -1:
196 segment = value[opened:i]
197 parts = segment.split(' ', 1)
200 elif len(parts) == 1:
205 if option is not None:
206 option = Option(option)
207 options[name] = option
212 class AnnotationApplier(object):
214 def __init__(self, blocks, transformer):
215 self._blocks = blocks
216 self._transformer = transformer
218 def _get_tag(self, block, tag_name):
222 return block.get(tag_name)
224 def parse(self, namespace):
225 for node in namespace.nodes:
226 self._parse_node(node)
228 # Boring parsing boilerplate.
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)
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)
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)
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)
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)
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)
287 def _parse_enum(self, enum):
288 block = self._blocks.get(enum.symbol)
289 self._parse_version(enum, block)
291 def _parse_constructors(self, constructors):
292 for ctor in constructors:
293 self._parse_function(ctor)
295 def _parse_fields(self, parent, fields):
297 self._parse_field(parent, field)
299 def _parse_properties(self, parent, properties):
300 for prop in properties:
301 self._parse_property(parent, prop)
303 def _parse_methods(self, methods):
304 for method in methods:
305 self._parse_function(method)
307 def _parse_signals(self, parent, signals):
308 for signal in signals:
309 self._parse_signal(parent, signal)
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)
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)
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)
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()
340 for i, param in enumerate(signal.parameters):
342 name, tag = names[i+1]
344 options = getattr(tag, 'options', {})
345 param_type = options.get(OPT_TYPE)
347 param.type.name = param_type.one()
350 self._parse_param(signal, param, tag)
351 self._parse_return(signal, signal.retval, block)
353 def _parse_field(self, parent, field):
354 if isinstance(field, Callback):
355 self._parse_callback(field)
357 def _parse_params(self, parent, params, block):
359 tag = self._get_tag(block, param.name)
360 self._parse_param(parent, param, tag)
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)
367 def _parse_param(self, parent, param, tag):
368 options = getattr(tag, 'options', {})
370 if isinstance(parent, Function):
371 scope = options.get(OPT_SCOPE)
373 param.scope = scope.one()
374 param.transfer = PARAM_TRANSFER_NONE
375 self._parse_param_ret_common(parent, param, options)
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
389 assert node.transfer is not None
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
400 direction = node.direction
403 def _guess_array(self, node):
404 ctype = node.type.ctype
407 if not ctype.endswith('*'):
409 if node.type.canonical in default_array_types:
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
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):
426 container_type = self._parse_array(parent, node, options)
427 elif has_element_type:
428 container_type = self._parse_element_type(parent, node, options)
430 container_type = None
432 return container_type
434 def _parse_array(self, parent, node, options):
435 array_opt = options.get(OPT_ARRAY)
437 array_values = array_opt.all()
441 element_type = options.get(OPT_ELEMENT_TYPE)
442 if element_type is not None:
443 element_type_name = element_type.one()
445 element_type_name = node.type.name
447 container_type = Array(node.type.ctype,
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
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
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(
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(
484 self._transformer.resolve_param_type(key_type),
485 self._transformer.resolve_param_type(value_type))
487 print 'FIXME: unhandled element-type container:', node
488 return container_type
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)
495 transfer = transfer_opt.one()
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))
506 def _parse_version(self, node, block):
507 since_tag = self._get_tag(block, TAG_SINCE)
508 if since_tag is None:
510 node.version = since_tag.value
512 def _parse_deprecated(self, node, block):
513 deprecated_tag = self._get_tag(block, TAG_DEPRECATED)
514 if deprecated_tag is None:
516 value = deprecated_tag.value
518 version, desc = value.split(': ')
522 node.deprecated = desc
523 if version is not None:
524 node.deprecated_version = version
526 def _guess_direction(self, node):
528 return node.direction
531 is_pointer = '*' in node.type.ctype
533 if is_pointer and node.type.name in BASIC_GIR_TYPES:
534 return PARAM_DIRECTION_OUT
536 return PARAM_DIRECTION_IN
538 def _guess_transfer(self, node, options):
539 if node.transfer is not None:
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
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
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
568 return PARAM_TRANSFER_FULL
569 elif isinstance(node, Field):
570 return PARAM_TRANSFER_NONE
572 raise AssertionError(node)