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 # All gtk-doc comments needs to start with this:
24 _COMMENT_HEADER = '*\n '
26 from .ast import (Array, Callback, Class, Enum, Field, Function, Interface,
27 List, Map, Parameter, Record, Return, Type, Union, Varargs,
30 PARAM_DIRECTION_INOUT,
34 PARAM_TRANSFER_CONTAINER,
37 from .odict import odict
38 from .glibast import GLibBoxed
41 class InvalidAnnotationError(Exception):
45 class DocBlock(object):
47 def __init__(self, name):
53 return '<DocBlock %r>' % (self.name, )
57 value = self.tags.get(name)
59 return self.tags.get('Return value')
63 return self.tags.get(name)
68 def __init__(self, name):
75 def __init__(self, option):
78 for p in option.split(' '):
80 name, value = p.split('=', 1)
84 self._dict[name] = value
86 self._array.append(name)
88 self._array.append((name, value))
91 return '<Option %r>' % (self._array, )
94 assert len(self._array) == 1
104 class AnnotationParser(object):
106 def __init__(self, namespace, source_scanner, transformer):
108 self._namespace = namespace
109 self._transformer = transformer
110 for comment in source_scanner.get_comments():
111 self._parse_comment(comment)
114 aa = AnnotationApplier(self._blocks, self._transformer)
115 aa.parse(self._namespace)
117 def _parse_comment(self, comment):
118 comment = comment.lstrip()
119 if not comment.startswith(_COMMENT_HEADER):
121 comment = comment[len(_COMMENT_HEADER):]
122 comment = comment.strip()
123 if not comment.startswith('* '):
125 comment = comment[2:]
127 pos = comment.index('\n ')
129 block_name = comment[:pos]
130 block_name = block_name.strip()
131 if not block_name.endswith(':'):
133 block = DocBlock(block_name[:-1])
134 content = comment[pos+1:]
135 for line in content.split('\n'):
137 line = line[2:].strip() # Skip ' *'
141 if line.startswith('@'):
143 elif not ': ' in line:
145 tag = self._parse_tag(line)
146 block.tags[tag.name] = tag
148 self._blocks[block.name] = block
150 def _parse_tag(self, value):
153 parts = value.split(': ', 1)
158 tag_name, options = parts
159 tag = DocTag(tag_name)
161 tag.options = self._parse_options(options)
164 def _parse_options(self, value):
169 for i, c in enumerate(value):
170 if c == '(' and opened == -1:
172 if c == ')' and opened != -1:
173 segment = value[opened:i]
174 parts = segment.split(' ', 1)
177 elif len(parts) == 1:
182 if option is not None:
183 option = Option(option)
184 options[name] = option
189 class AnnotationApplier(object):
191 def __init__(self, blocks, transformer):
192 self._blocks = blocks
193 self._transformer = transformer
195 def _get_tag(self, block, tag_name):
199 return block.get(tag_name)
201 def parse(self, namespace):
202 for node in namespace.nodes:
203 self._parse_node(node)
205 # Boring parsing boilerplate.
207 def _parse_node(self, node):
208 if isinstance(node, Function):
209 self._parse_function(node)
210 elif isinstance(node, Enum):
211 self._parse_enum(node)
212 elif isinstance(node, Class):
213 self._parse_class(node)
214 elif isinstance(node, Interface):
215 self._parse_interface(node)
216 elif isinstance(node, Callback):
217 self._parse_callback(node)
218 elif isinstance(node, Record):
219 self._parse_record(node)
220 elif isinstance(node, Union):
221 self._parse_union(node)
222 elif isinstance(node, GLibBoxed):
223 self._parse_boxed(node)
225 def _parse_class(self, class_):
226 block = self._blocks.get(class_.name)
227 self._parse_version(class_, block)
228 self._parse_constructors(class_.constructors)
229 self._parse_methods(class_.methods)
230 self._parse_methods(class_.static_methods)
231 self._parse_properties(class_, class_.properties)
232 self._parse_signals(class_, class_.signals)
233 self._parse_fields(class_, class_.fields)
235 def _parse_interface(self, interface):
236 block = self._blocks.get(interface.name)
237 self._parse_version(interface, block)
238 self._parse_methods(interface.methods)
239 self._parse_properties(interface, interface.properties)
240 self._parse_signals(interface, interface.signals)
241 self._parse_fields(interface, interface.fields)
243 def _parse_record(self, record):
244 block = self._blocks.get(record.symbol)
245 self._parse_version(record, block)
246 self._parse_constructors(record.constructors)
247 self._parse_fields(record, record.fields)
248 if isinstance(record, GLibBoxed):
249 self._parse_methods(record.methods)
251 def _parse_boxed(self, boxed):
252 block = self._blocks.get(boxed.name)
253 self._parse_version(boxed, block)
254 self._parse_constructors(boxed.constructors)
255 self._parse_methods(boxed.methods)
257 def _parse_union(self, union):
258 block = self._blocks.get(union.name)
259 self._parse_fields(union, union.fields)
260 self._parse_constructors(union.constructors)
261 if isinstance(union, GLibBoxed):
262 self._parse_methods(union.methods)
264 def _parse_enum(self, enum):
265 block = self._blocks.get(enum.symbol)
266 self._parse_version(enum, block)
268 def _parse_constructors(self, constructors):
269 for ctor in constructors:
270 self._parse_function(ctor)
272 def _parse_fields(self, parent, fields):
274 self._parse_field(parent, field)
276 def _parse_properties(self, parent, properties):
277 for prop in properties:
278 self._parse_property(parent, prop)
280 def _parse_methods(self, methods):
281 for method in methods:
282 self._parse_function(method)
284 def _parse_signals(self, parent, signals):
285 for signal in signals:
286 self._parse_signal(parent, signal)
288 def _parse_property(self, parent, prop):
289 block = self._blocks.get('%s:%s' % (parent.type_name, prop.name))
290 self._parse_version(prop, block)
291 self._parse_deprecated(prop, block)
293 def _parse_callback(self, callback):
294 block = self._blocks.get(callback.ctype)
295 self._parse_version(callback, block)
296 self._parse_params(callback, callback.parameters, block)
297 self._parse_return(callback, callback.retval, block)
299 def _parse_function(self, func):
300 block = self._blocks.get(func.symbol)
301 self._parse_version(func, block)
302 self._parse_deprecated(func, block)
303 self._parse_params(func, func.parameters, block)
304 self._parse_return(func, func.retval, block)
306 def _parse_signal(self, parent, signal):
307 block = self._blocks.get('%s::%s' % (parent.type_name, signal.name))
308 self._parse_version(signal, block)
309 self._parse_deprecated(signal, block)
310 # We're only attempting to name the signal parameters if
311 # the number of parameter tags (@foo) is the same or greater
312 # than the number of signal parameters
313 if block and len(block.tags) > len(signal.parameters):
314 names = block.tags.items()
317 for i, param in enumerate(signal.parameters):
319 name, tag = names[i+1]
321 options = getattr(tag, 'options', {})
322 param_type = options.get('type')
324 param.type.name = param_type.one()
327 self._parse_param(signal, param, tag)
328 self._parse_return(signal, signal.retval, block)
330 def _parse_field(self, parent, field):
331 if isinstance(field, Callback):
332 self._parse_callback(field)
334 def _parse_params(self, parent, params, block):
336 tag = self._get_tag(block, param.name)
337 self._parse_param(parent, param, tag)
339 def _parse_return(self, parent, return_, block):
340 tag = self._get_tag(block, 'Returns')
341 options = getattr(tag, 'options', {})
342 self._parse_param_ret_common(parent, return_, options)
344 def _parse_param(self, parent, param, tag):
345 options = getattr(tag, 'options', {})
347 if isinstance(parent, Function):
348 scope = options.get('scope')
350 param.scope = scope.one()
351 param.transfer = PARAM_TRANSFER_NONE
352 self._parse_param_ret_common(parent, param, options)
354 def _parse_param_ret_common(self, parent, node, options):
355 node.direction = self._extract_direction(node, options)
356 container_type = self._extract_container_type(
357 parent, node, options)
358 if container_type is not None:
359 node.type = container_type
360 if node.direction is None:
361 node.direction = self._guess_direction(node)
362 node.transfer = self._extract_transfer(parent, node, options)
363 if 'allow-none' in options:
364 node.allow_none = True
366 assert node.transfer is not None
368 def _extract_direction(self, node, options):
369 if ('inout' in options or
370 'in-out' in options):
371 direction = PARAM_DIRECTION_INOUT
372 elif 'out' in options:
373 direction = PARAM_DIRECTION_OUT
374 elif 'in' in options:
375 direction = PARAM_DIRECTION_IN
377 direction = node.direction
380 def _guess_array(self, node):
381 ctype = node.type.ctype
384 if not ctype.endswith('*'):
386 if node.type.canonical in default_array_types:
390 def _extract_container_type(self, parent, node, options):
391 has_element_type = 'element-type' in options
392 has_array = 'array' in options
394 # FIXME: This is a hack :-(
395 if (not isinstance(node, Field) and
396 (not has_element_type and
397 (node.direction is None
398 or node.direction == PARAM_DIRECTION_IN))):
399 if self._guess_array(node):
403 container_type = self._parse_array(parent, node, options)
404 elif has_element_type:
405 container_type = self._parse_element_type(parent, node, options)
407 container_type = None
409 return container_type
411 def _parse_array(self, parent, node, options):
412 array_opt = options.get('array')
414 array_values = array_opt.all()
418 element_type = options.get('element-type')
419 if element_type is not None:
420 element_type_name = element_type.one()
422 element_type_name = node.type.name
424 container_type = Array(node.type.ctype,
426 if 'zero-terminated' in array_values:
427 container_type.zeroterminated = array_values.get(
428 'zero-terminated') == '1'
429 length = array_values.get('length')
430 if length is not None:
431 param_index = parent.get_parameter_index(length)
432 container_type.length_param_index = param_index
433 # For in parameters we're incorrectly deferring
434 # char/unsigned char to utf8 when a length annotation
436 if (isinstance(node, Parameter) and
437 node.type.name == 'utf8' and
438 self._guess_direction(node) == PARAM_DIRECTION_IN):
439 # FIXME: unsigned char/guchar should be uint8
440 container_type.element_type = 'int8'
441 container_type.size = array_values.get('fized-size')
442 return container_type
444 def _parse_element_type(self, parent, node, options):
445 element_type_opt = options.get('element-type')
446 element_type = element_type_opt.flat()
447 if node.type.name in ['GLib.List', 'GLib.SList']:
448 assert len(element_type) == 1
449 etype = Type(element_type[0])
450 container_type = List(
453 self._transformer.resolve_param_type(etype))
454 elif node.type.name in ['GLib.HashTable']:
455 assert len(element_type) == 2
456 key_type = Type(element_type[0])
457 value_type = Type(element_type[1])
458 container_type = Map(
461 self._transformer.resolve_param_type(key_type),
462 self._transformer.resolve_param_type(value_type))
464 print 'FIXME: unhandled element-type container:', node
465 return container_type
467 def _extract_transfer(self, parent, node, options):
468 transfer_opt = options.get('transfer')
469 if transfer_opt is None:
470 transfer = self._guess_transfer(node, options)
472 transfer = transfer_opt.one()
474 transfer = PARAM_TRANSFER_FULL
475 if transfer not in [PARAM_TRANSFER_NONE,
476 PARAM_TRANSFER_CONTAINER,
477 PARAM_TRANSFER_FULL]:
478 raise InvalidAnnotationError(
479 "transfer for %s of %r is invalid (%r), must be one of "
480 "none, container, full." % (node, parent.name, transfer))
483 def _parse_version(self, node, block):
484 since_tag = self._get_tag(block, 'Since')
485 if since_tag is None:
487 node.version = since_tag.value
489 def _parse_deprecated(self, node, block):
490 deprecated_tag = self._get_tag(block, 'Deprecated')
491 if deprecated_tag is None:
493 value = deprecated_tag.value
495 version, desc = value.split(': ')
499 node.deprecated = desc
500 if version is not None:
501 node.deprecated_version = version
503 def _guess_direction(self, node):
505 return node.direction
508 is_pointer = '*' in node.type.ctype
510 if is_pointer and node.type.name in BASIC_GIR_TYPES:
511 return PARAM_DIRECTION_OUT
513 return PARAM_DIRECTION_IN
515 def _guess_transfer(self, node, options):
516 if node.transfer is not None:
519 if isinstance(node.type, Array):
520 return PARAM_TRANSFER_NONE
521 # Anything with 'const' gets none
522 if node.type.is_const:
523 return PARAM_TRANSFER_NONE
525 elif node.type.name in [TYPE_NONE, TYPE_ANY]:
526 return PARAM_TRANSFER_NONE
527 elif isinstance(node.type, Varargs):
528 return PARAM_TRANSFER_NONE
529 elif isinstance(node, Parameter):
530 if node.direction in [PARAM_DIRECTION_INOUT,
531 PARAM_DIRECTION_OUT]:
532 return PARAM_TRANSFER_FULL
533 # This one is a hack for compatibility; the transfer
534 # for string parameters really has no defined meaning.
535 elif node.type.canonical == 'utf8':
536 return PARAM_TRANSFER_FULL
538 return PARAM_TRANSFER_NONE
539 elif isinstance(node, Return):
540 if (node.type.canonical in BASIC_GIR_TYPES or
541 (node.type.canonical in [TYPE_NONE, TYPE_ANY] and
542 node.type.is_const)):
543 return PARAM_TRANSFER_NONE
545 return PARAM_TRANSFER_FULL
546 elif isinstance(node, Field):
547 return PARAM_TRANSFER_NONE
549 raise AssertionError(node)