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 .glibast import GLibBoxed
40 class InvalidAnnotationError(Exception):
44 class DocBlock(object):
46 def __init__(self, name):
52 return '<Directive %r>' % (self.name, )
56 value = self.tags.get(name)
58 return self.tags.get('Return value')
62 return self.tags.get(name)
67 def __init__(self, name):
74 def __init__(self, option):
77 for p in option.split(' '):
79 name, value = p.split('=', 1)
83 self._dict[name] = value
85 self._array.append(name)
87 self._array.append((name, value))
90 return '<Option %r>' % (self._array, )
93 assert len(self._array) == 1
103 class AnnotationParser(object):
105 def __init__(self, namespace, source_scanner, transformer):
107 self._namespace = namespace
108 self._transformer = transformer
109 for comment in source_scanner.get_comments():
110 self._parse_comment(comment)
113 aa = AnnotationApplier(self._blocks, self._transformer)
114 aa.parse(self._namespace)
116 def _parse_comment(self, comment):
117 if not comment.startswith(_COMMENT_HEADER):
119 comment = comment[len(_COMMENT_HEADER):]
120 pos = comment.index('\n ')
122 block_name = comment[:pos]
123 block_name = block_name.strip()
124 if not block_name.endswith(':'):
126 block = DocBlock(block_name[:-1])
127 content = comment[pos+1:]
128 for line in content.split('\n'):
129 line = line[2:].strip() # Skip ' *'
133 if line.startswith('@'):
135 elif not ': ' in line:
137 tag = self._parse_tag(line)
138 block.tags[tag.name] = tag
140 self._blocks[block.name] = block
142 def _parse_tag(self, value):
145 parts = value.split(': ', 1)
150 tag_name, options = parts
151 tag = DocTag(tag_name)
153 tag.options = self._parse_options(options)
156 def _parse_options(self, value):
161 for i, c in enumerate(value):
162 if c == '(' and opened == -1:
164 if c == ')' and opened != -1:
165 segment = value[opened:i]
166 parts = segment.split(' ', 1)
169 elif len(parts) == 1:
174 if option is not None:
175 option = Option(option)
176 options[name] = option
181 class AnnotationApplier(object):
183 def __init__(self, blocks, transformer):
184 self._blocks = blocks
185 self._transformer = transformer
187 def _get_tag(self, block, tag_name):
191 return block.get(tag_name)
193 def parse(self, namespace):
194 for node in namespace.nodes:
195 self._parse_node(node)
197 # Boring parsing boilerplate.
199 def _parse_node(self, node):
200 if isinstance(node, Function):
201 self._parse_function(node)
202 elif isinstance(node, Enum):
203 self._parse_enum(node)
204 elif isinstance(node, Class):
205 self._parse_class(node)
206 elif isinstance(node, Interface):
207 self._parse_interface(node)
208 elif isinstance(node, Callback):
209 self._parse_callback(node)
210 elif isinstance(node, Record):
211 self._parse_record(node)
212 elif isinstance(node, Union):
213 self._parse_union(node)
214 elif isinstance(node, GLibBoxed):
215 self._parse_boxed(node)
217 def _parse_class(self, class_):
218 block = self._blocks.get(class_.name)
219 self._parse_version(class_, block)
220 self._parse_constructors(class_.constructors)
221 self._parse_methods(class_.methods)
222 self._parse_methods(class_.static_methods)
223 self._parse_properties(class_.properties)
224 self._parse_signals(class_.signals)
225 self._parse_fields(class_, class_.fields)
227 def _parse_interface(self, interface):
228 block = self._blocks.get(interface.name)
229 self._parse_version(interface, block)
230 self._parse_methods(interface.methods)
231 self._parse_properties(interface.properties)
232 self._parse_signals(interface.signals)
233 self._parse_fields(interface, interface.fields)
235 def _parse_record(self, record):
236 block = self._blocks.get(record.symbol)
237 self._parse_version(record, block)
238 self._parse_constructors(record.constructors)
239 self._parse_fields(record, record.fields)
240 if isinstance(record, GLibBoxed):
241 self._parse_methods(record.methods)
243 def _parse_boxed(self, boxed):
244 block = self._blocks.get(boxed.name)
245 self._parse_version(boxed, block)
246 self._parse_constructors(boxed.constructors)
247 self._parse_methods(boxed.methods)
249 def _parse_union(self, union):
250 block = self._blocks.get(union.name)
251 self._parse_fields(union, union.fields)
252 self._parse_constructors(union.constructors)
253 if isinstance(union, GLibBoxed):
254 self._parse_methods(union.methods)
256 def _parse_enum(self, enum):
257 block = self._blocks.get(enum.symbol)
258 self._parse_version(enum, block)
260 def _parse_constructors(self, constructors):
261 for ctor in constructors:
262 self._parse_function(ctor)
264 def _parse_fields(self, parent, fields):
266 self._parse_field(parent, field)
268 def _parse_properties(self, properties):
269 for prop in properties:
270 self._parse_property(property)
272 def _parse_methods(self, methods):
273 for method in methods:
274 self._parse_function(method)
276 def _parse_signals(self, signals):
277 for signal in signals:
278 self._parse_signal(signal)
280 def _parse_property(self, prop):
283 def _parse_callback(self, callback):
284 block = self._blocks.get(callback.ctype)
285 self._parse_version(callback, block)
286 self._parse_params(callback, callback.parameters, block)
287 self._parse_return(callback, callback.retval, block)
289 def _parse_function(self, func):
290 block = self._blocks.get(func.symbol)
291 self._parse_version(func, block)
292 self._parse_deprecated(func, block)
293 self._parse_params(func, func.parameters, block)
294 self._parse_return(func, func.retval, block)
296 def _parse_signal(self, signal):
297 block = self._blocks.get(signal.name)
298 self._parse_version(signal, block)
299 self._parse_params(signal, signal.parameters, block)
300 self._parse_return(signal, signal.retval, block)
302 def _parse_field(self, parent, field):
303 if isinstance(field, Callback):
304 self._parse_callback(field)
306 def _parse_params(self, parent, params, block):
308 self._parse_param(parent, param, block)
310 def _parse_return(self, parent, return_, block):
311 tag = self._get_tag(block, 'Returns')
312 options = getattr(tag, 'options', {})
313 self._parse_param_ret_common(parent, return_, options)
315 def _parse_param(self, parent, param, block):
316 tag = self._get_tag(block, param.name)
317 options = getattr(tag, 'options', {})
319 if isinstance(parent, Function):
320 scope = options.get('scope')
322 param.scope = scope.one()
323 param.transfer = PARAM_TRANSFER_NONE
324 self._parse_param_ret_common(parent, param, options)
326 def _parse_param_ret_common(self, parent, node, options):
327 node.direction = self._extract_direction(node, options)
328 container_type = self._extract_container_type(
329 parent, node, options)
330 if container_type is not None:
331 node.type = container_type
332 if not node.direction:
333 node.direction = self._guess_direction(node)
334 node.transfer = self._extract_transfer(parent, node, options)
335 if 'allow-none' in options:
336 node.allow_none = True
338 assert node.transfer is not None
340 def _extract_direction(self, node, options):
341 if ('inout' in options or
342 'in-out' in options):
343 direction = PARAM_DIRECTION_INOUT
344 elif 'out' in options:
345 direction = PARAM_DIRECTION_OUT
346 elif 'in' in options:
347 direction = PARAM_DIRECTION_IN
349 direction = node.direction
352 def _guess_array(self, node):
353 ctype = node.type.ctype
356 if not ctype.endswith('*'):
358 if node.type.canonical in default_array_types:
362 def _extract_container_type(self, parent, node, options):
363 has_element_type = 'element-type' in options
364 has_array = 'array' in options
366 # FIXME: This is a hack :-(
367 if (not isinstance(node, Field) and
368 (not has_element_type and
369 (node.direction is None
370 or node.direction == PARAM_DIRECTION_IN))):
371 if self._guess_array(node):
375 container_type = self._parse_array(parent, node, options)
376 elif has_element_type:
377 container_type = self._parse_element_type(parent, node, options)
379 container_type = None
381 return container_type
383 def _parse_array(self, parent, node, options):
384 array_opt = options.get('array')
386 values = array_opt.all()
389 container_type = Array(node.type.ctype, node.type.name)
390 if 'zero-terminated' in values:
391 container_type.zeroterminated = values.get(
392 'zero-terminated') == '1'
393 length = values.get('length')
394 if length is not None:
395 param_index = parent.get_parameter_index(length)
396 container_type.length_param_index = param_index
397 container_type.size = values.get('fized-size')
398 return container_type
400 def _parse_element_type(self, parent, node, options):
401 element_type_opt = options.get('element-type')
402 element_type = element_type_opt.flat()
403 if node.type.name in ['GLib.List', 'GLib.SList']:
404 assert len(element_type) == 1
405 etype = Type(element_type[0])
406 container_type = List(
409 self._transformer.resolve_param_type(etype))
410 elif node.type.name in ['GLib.HashTable']:
411 assert len(element_type) == 2
412 key_type = Type(element_type[0])
413 value_type = Type(element_type[1])
414 container_type = Map(
417 self._transformer.resolve_param_type(key_type),
418 self._transformer.resolve_param_type(value_type))
420 print 'FIXME: unhandled element-type container:', node
421 return container_type
423 def _extract_transfer(self, parent, node, options):
424 transfer_opt = options.get('transfer')
425 if transfer_opt is None:
426 transfer = self._guess_transfer(node, options)
428 transfer = transfer_opt.one()
430 transfer = PARAM_TRANSFER_FULL
431 if transfer not in [PARAM_TRANSFER_NONE,
432 PARAM_TRANSFER_CONTAINER,
433 PARAM_TRANSFER_FULL]:
434 raise InvalidAnnotationError(
435 "transfer for %s of %r is invalid (%r), must be one of "
436 "none, container, full." % (node, parent.name, transfer))
439 def _parse_version(self, node, block):
440 since_tag = self._get_tag(block, 'Since')
441 if since_tag is None:
443 node.version = since_tag.value
445 def _parse_deprecated(self, node, block):
446 deprecated_tag = self._get_tag(block, 'Deprecated')
447 if deprecated_tag is None:
449 value = deprecated_tag.value
451 version, desc = value.split(': ')
455 node.deprecated = desc
456 if version is not None:
457 node.deprecated_version = version
459 def _guess_direction(self, node):
461 return node.direction
464 is_pointer = '*' in node.type.ctype
466 if is_pointer and node.type.name in BASIC_GIR_TYPES:
467 return PARAM_DIRECTION_OUT
469 return PARAM_DIRECTION_IN
471 def _guess_transfer(self, node, options):
472 if node.transfer is not None:
475 if isinstance(node.type, Array):
476 return PARAM_TRANSFER_NONE
477 # Anything with 'const' gets none
478 if node.type.is_const:
479 return PARAM_TRANSFER_NONE
481 elif node.type.name in [TYPE_NONE, TYPE_ANY]:
482 return PARAM_TRANSFER_NONE
483 elif isinstance(node.type, Varargs):
484 return PARAM_TRANSFER_NONE
485 elif isinstance(node, Parameter):
486 if node.direction in [PARAM_DIRECTION_INOUT,
487 PARAM_DIRECTION_OUT]:
488 return PARAM_TRANSFER_FULL
489 # This one is a hack for compatibility; the transfer
490 # for string parameters really has no defined meaning.
491 elif node.type.canonical == 'utf8':
492 return PARAM_TRANSFER_FULL
494 return PARAM_TRANSFER_NONE
495 elif isinstance(node, Return):
496 if (node.type.canonical in BASIC_GIR_TYPES or
497 (node.type.canonical in [TYPE_NONE, TYPE_ANY] and
498 node.type.is_const)):
499 return PARAM_TRANSFER_NONE
501 return PARAM_TRANSFER_FULL
502 elif isinstance(node, Field):
503 return PARAM_TRANSFER_NONE
505 raise AssertionError(node)