# AnnotationParser - parses gtk-doc annotations
+import re
import sys
from .ast import (Array, Bitfield, Callback, Class, Enum, Field, Function,
OPT_TYPE = 'type'
OPT_CLOSURE = 'closure'
OPT_DESTROY = 'destroy'
+OPT_SKIP = 'skip'
# Specific option values
OPT_VAL_BITFIELD = 'bitfield'
OPT_ARRAY_LENGTH = 'length'
OPT_ARRAY_ZERO_TERMINATED = 'zero-terminated'
+OPT_SCOPE_ASYNC = 'async'
+OPT_SCOPE_CALL = 'call'
+OPT_SCOPE_NOTIFIED = 'notified'
class InvalidAnnotationError(Exception):
pass
tag_name, value = self._split_tag_namevalue(line)
canon_name = tag_name.lower()
if canon_name in block.tags:
- print >>sys.stderr, "Multiple definition of tag %r" \
- % (canon_name, )
+ print >> sys.stderr, (
+ "Symbol %s has multiple definition of tag %r" % (
+ block_name, canon_name, ))
block.tags[canon_name] = self._create_tag(canon_name, value)
block.comment = '\n'.join(comment_lines)
self._blocks[block.name] = block
if len(parts) == 1:
tag_name = parts[0]
value = ''
+ if tag_name.endswith(':'):
+ tag_name = tag_name[:-1]
else:
tag_name, value = parts
return (tag_name, value)
self._parse_node_common(record, block)
self._parse_constructors(record.constructors)
self._parse_methods(record, record.methods)
- self._parse_fields(record, record.fields)
+ self._parse_fields(record, record.fields, block)
if block:
record.doc = block.comment
def _parse_union(self, union):
block = self._blocks.get(union.name)
self._parse_node_common(union, block)
- self._parse_fields(union, union.fields)
+ self._parse_fields(union, union.fields, block)
self._parse_constructors(union.constructors)
self._parse_methods(union, union.methods)
if block:
for ctor in constructors:
self._parse_function(ctor)
- def _parse_fields(self, parent, fields):
+ def _parse_fields(self, parent, fields, block=None):
for field in fields:
- self._parse_field(parent, field)
+ self._parse_field(parent, field, block)
def _parse_properties(self, parent, properties):
for prop in properties:
def _parse_callable(self, callable, block):
self._parse_node_common(callable, block)
+ for i, param in enumerate(callable.parameters):
+ if (param.type.ctype != 'GDestroyNotify' and
+ param.type.name != 'GLib.DestroyNotify'):
+ continue
+ if i < 2:
+ break
+ callback_param = callable.parameters[i-2]
+ if callback_param.closure_index != -1:
+ callback_param.scope = OPT_SCOPE_NOTIFIED
+ callback_param.transfer = PARAM_TRANSFER_NONE
+
self._parse_params(callable, callable.parameters, block)
self._parse_return(callable, callable.retval, block)
if block:
# We're only attempting to name the signal parameters if
# the number of parameter tags (@foo) is the same or greater
# than the number of signal parameters
- resolve = self._transformer.resolve_param_type
if block and len(block.tags) > len(signal.parameters):
names = block.tags.items()
else:
options = getattr(tag, 'options', {})
param_type = options.get(OPT_TYPE)
if param_type:
- param.type.name = resolve(param_type.one())
+ param.type = self._resolve(param_type.one(), param.type)
else:
tag = None
self._parse_param(signal, param, tag)
key = '%s::%s' % (parent.type_name, vfunc.name)
self._parse_callable(vfunc, self._blocks.get(key))
- def _parse_field(self, parent, field):
+ def _parse_field(self, parent, field, block=None):
if isinstance(field, Callback):
self._parse_callback(field)
+ else:
+ if not block:
+ return
+ tag = block.get(field.name)
+ if not tag:
+ return
+ t = tag.options.get('type')
+ if not t:
+ return
+ field.type.name = self._transformer.resolve_type_name(t.one())
def _parse_params(self, parent, params, block):
for param in params:
if scope:
param.scope = scope.one()
param.transfer = PARAM_TRANSFER_NONE
+ elif (param.type.ctype == 'GAsyncReadyCallback' or
+ param.type.name == 'Gio.AsyncReadyCallback'):
+ param.scope = OPT_SCOPE_ASYNC
+ param.transfer = PARAM_TRANSFER_NONE
+
destroy = options.get(OPT_DESTROY)
if destroy:
param.destroy_index = parent.get_parameter_index(destroy.one())
node.allow_none = True
param_type = options.get(OPT_TYPE)
if param_type:
- resolve = self._transformer.resolve_param_type
- node.type.name = resolve(param_type.one())
+ node.type = self._resolve(param_type.one(), node.type)
assert node.transfer is not None
if tag is not None and tag.comment is not None:
element_type = options.get(OPT_ELEMENT_TYPE)
if element_type is not None:
- element_type_name = element_type.one()
+ element_type_node = self._resolve(element_type.one())
else:
- element_type_name = node.type.name
+ element_type_node = Type(node.type.name) # erase ctype
container_type = Array(node.type.ctype,
- element_type_name)
+ element_type_node)
container_type.is_const = node.type.is_const
if OPT_ARRAY_ZERO_TERMINATED in array_values:
container_type.zeroterminated = array_values.get(
# is specified.
if (isinstance(node, Parameter) and
node.type.name == 'utf8' and
- self._guess_direction(node) == PARAM_DIRECTION_IN):
+ self._guess_direction(node) == PARAM_DIRECTION_IN and
+ element_type is None):
# FIXME: unsigned char/guchar should be uint8
- container_type.element_type = 'int8'
+ container_type.element_type = Type('int8')
container_type.size = array_values.get(OPT_ARRAY_FIXED_SIZE)
return container_type
+ def _resolve(self, type_str, orig_node=None):
+ def grab_one(type_str, resolver, top_combiner, combiner):
+ """Return a complete type, and the trailing string part after it.
+ Use resolver() on each identifier, and combiner() on the parts of
+ each complete type. (top_combiner is used on the top-most type.)"""
+ bits = re.split(r'([,<>])', type_str, 1)
+ first, sep, rest = [bits[0], '', ''] if (len(bits)==1) else bits
+ args = [resolver(first)]
+ if sep == '<':
+ while sep != '>':
+ next, rest = grab_one(rest, resolver, combiner, combiner)
+ args.append(next)
+ sep, rest = rest[0], rest[1:]
+ else:
+ rest = sep + rest
+ return top_combiner(*args), rest
+ def resolver(ident):
+ return self._transformer.resolve_param_type(Type(ident))
+ def combiner(base, *rest):
+ if not rest:
+ return base
+ if (base.name in ['GLib.List', 'GLib.SList'] or
+ base.ctype in ['GList*', 'GSList*']) and len(rest)==1:
+ return List(base.name, base.ctype, *rest)
+ if (base.name in ['GLib.HashTable'] or
+ base.ctype in ['GHashTable*']) and len(rest)==2:
+ return Map(base.name, base.ctype, *rest)
+ print "WARNING: throwing away type parameters:", type_str
+ return base
+ def top_combiner(base, *rest):
+ """For the top most type, recycle orig_node if possible."""
+ if orig_node is not None:
+ orig_node.name = base.name
+ base = orig_node # preserve other properties of orig_node
+ return combiner(base, *rest)
+
+ result, rest = grab_one(type_str, resolver, top_combiner, combiner)
+ if rest:
+ print "WARNING: throwing away trailing part of type:", type_str
+ return result
+
def _parse_element_type(self, parent, node, options):
element_type_opt = options.get(OPT_ELEMENT_TYPE)
element_type = element_type_opt.flat()
- if node.type.name in ['GLib.List', 'GLib.SList']:
+ if (node.type.name in ['GLib.List', 'GLib.SList'] or
+ node.type.ctype in ['GList*', 'GSList*']):
assert len(element_type) == 1
- etype = Type(element_type[0])
container_type = List(
node.type.name,
node.type.ctype,
- self._transformer.resolve_param_type(etype))
- elif node.type.name in ['GLib.HashTable']:
+ self._resolve(element_type[0]))
+ elif (node.type.name in ['GLib.HashTable'] or
+ node.type.ctype in ['GHashTable*']):
assert len(element_type) == 2
- key_type = Type(element_type[0])
- value_type = Type(element_type[1])
container_type = Map(
node.type.name,
node.type.ctype,
- self._transformer.resolve_param_type(key_type),
- self._transformer.resolve_param_type(value_type))
+ self._resolve(element_type[0]),
+ self._resolve(element_type[1]))
else:
print 'FIXME: unhandled element-type container:', node
return container_type
self._parse_version(node, block)
self._parse_deprecated(node, block)
self._parse_attributes(node, block)
+ self._parse_skip(node, block)
def _parse_version(self, node, block):
since_tag = self._get_tag(block, TAG_SINCE)
for key, value in annos_tag.options.iteritems():
node.attributes.append((key, value.one()))
+ def _parse_skip(self, node, block):
+ if block is not None:
+ if OPT_SKIP in block.options:
+ node.skip = True
+
def _parse_rename_to_func(self, node, block):
rename_to_tag = self._get_tag(block, TAG_RENAME_TO)
if rename_to_tag is None: