From 36aa515f1036978ced8d4ffb808260844f7229e0 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 27 Jul 2010 06:16:37 -0400 Subject: Major rewrite One of the first big changes in this rewrite is changing the Type object to have separate target_fundamental and target_giname properties, rather than just being strings. Previously in the scanner, it was awful because we used heuristics around strings. The ast.py is refactored so that not everything is a Node - that was a rather useless abstraction. Now, only things which can have a GIName are Node. E.g. Type and Field are no longer Node. More things were merged from glibast.py into ast.py, since it isn't a very useful split. transformer.py gains more intelligence and will e.g. turn GLib.List into a List() object earlier. The namespace processing is a lot cleaner now; since we parse the included .girs, we know the C prefix for each namespace, and have functions to parse both C type names (GtkFooBar) and symbols gtk_foo_bar into their symbols cleanly. Type resolution is much, much saner because we know Type(target_giname=Gtk.Foo) maps to the namespace Gtk. glibtransformer.py now just handles the XML processing from the dump, and a few miscellaneous things. The major heavy lifting now lives in primarytransformer.py, which is a combination of most of annotationparser.py and half of glibtransformer.py. annotationparser.py now literally just parses annotations; it's no longer in the business of e.g. guessing transfer too. finaltransformer.py is a new file which does post-analysis for "introspectability" mainly. girparser.c is fixed for some introspectable=0 processing. --- giscanner/Makefile.am | 6 +- giscanner/annotationparser.py | 734 +----------------------- giscanner/ast.py | 752 +++++++++++++++--------- giscanner/codegen.py | 137 +++++ giscanner/dumper.py | 2 +- giscanner/gdumpparser.py | 455 +++++++++++++++ giscanner/girparser.py | 487 ++++++++++------ giscanner/girwriter.py | 235 ++++---- giscanner/glibast.py | 91 +-- giscanner/glibtransformer.py | 1205 --------------------------------------- giscanner/introspectablepass.py | 159 ++++++ giscanner/maintransformer.py | 933 ++++++++++++++++++++++++++++++ giscanner/scannermain.py | 169 +++--- giscanner/sourcescanner.py | 10 +- giscanner/testcodegen.py | 119 ++++ giscanner/transformer.py | 749 ++++++++++++------------ giscanner/utils.py | 13 + 17 files changed, 3305 insertions(+), 2951 deletions(-) create mode 100644 giscanner/codegen.py create mode 100644 giscanner/gdumpparser.py delete mode 100644 giscanner/glibtransformer.py create mode 100644 giscanner/introspectablepass.py create mode 100644 giscanner/maintransformer.py create mode 100644 giscanner/testcodegen.py (limited to 'giscanner') diff --git a/giscanner/Makefile.am b/giscanner/Makefile.am index 642bcbc5..708fdf8a 100644 --- a/giscanner/Makefile.am +++ b/giscanner/Makefile.am @@ -36,18 +36,22 @@ pkgpyexec_PYTHON = \ annotationparser.py \ ast.py \ cachestore.py \ + codegen.py \ config.py \ dumper.py \ + introspectablepass.py \ girparser.py \ girwriter.py \ glibast.py \ - glibtransformer.py \ + gdumpparser.py \ libtoolimporter.py \ minixpath.py \ odict.py \ + primarytransformer.py \ shlibs.py \ scannermain.py \ sourcescanner.py \ + testcodegen.py \ transformer.py \ utils.py \ xmlwriter.py diff --git a/giscanner/annotationparser.py b/giscanner/annotationparser.py index 9e4340fa..fc5f77d6 100644 --- a/giscanner/annotationparser.py +++ b/giscanner/annotationparser.py @@ -22,23 +22,7 @@ import re -from .ast import (Array, Bitfield, Callback, Class, Enum, Field, Function, - Interface, List, Map, Parameter, Property, Record, Return, - Type, Union, Varargs, - default_array_types, - BASIC_GIR_TYPES, - PARAM_DIRECTION_INOUT, - PARAM_DIRECTION_IN, - PARAM_DIRECTION_OUT, - PARAM_SCOPE_CALL, - PARAM_SCOPE_ASYNC, - PARAM_SCOPE_NOTIFIED, - PARAM_TRANSFER_NONE, - PARAM_TRANSFER_CONTAINER, - PARAM_TRANSFER_FULL, - TYPE_ANY, TYPE_NONE) from .odict import odict -from .glibast import GLibBoxed # All gtk-doc comments needs to start with this: _COMMENT_HEADER = '*\n ' @@ -73,9 +57,6 @@ OPT_DESTROY = 'destroy' OPT_SKIP = 'skip' OPT_FOREIGN = 'foreign' -# Specific option values -OPT_VAL_BITFIELD = 'bitfield' - # Array options - array specific annotations OPT_ARRAY_FIXED_SIZE = 'fixed-size' OPT_ARRAY_LENGTH = 'length' @@ -85,10 +66,6 @@ OPT_SCOPE_ASYNC = 'async' OPT_SCOPE_CALL = 'call' OPT_SCOPE_NOTIFIED = 'notified' -class InvalidAnnotationError(Exception): - pass - - class DocBlock(object): def __init__(self, name, options): @@ -153,16 +130,14 @@ class AnnotationParser(object): OPTION_RE = re.compile(r'\([A-Za-z]+[^(]*\)') RETURNS_RE = re.compile(r'^return(s?)( value)?:', re.IGNORECASE) - def __init__(self, namespace, source_scanner, transformer): + def __init__(self, source_scanner): self._blocks = {} - self._namespace = namespace - self._transformer = transformer - for comment in source_scanner.get_comments(): - self._parse_comment(comment) + self._source_scanner = source_scanner def parse(self): - aa = AnnotationApplier(self._blocks, self._transformer) - aa.parse(self._namespace) + for comment in self._source_scanner.get_comments(): + self._parse_comment(comment) + return self._blocks def _parse_comment(self, comment): # We're looking for gtk-doc comments here, they look like this: @@ -199,7 +174,6 @@ class AnnotationParser(object): else: block_name, block_options = block_header, {} block = DocBlock(block_name, block_options) - debug = block_name == 'annotation_object_compute_sum_n' comment_lines = [] parsing_parameters = True last_param_tag = None @@ -311,701 +285,3 @@ class AnnotationParser(object): opened = -1 return options - - -class AnnotationApplier(object): - - def __init__(self, blocks, transformer): - self._blocks = blocks - self._transformer = transformer - - def _get_tag(self, block, tag_name): - if block is None: - return None - - return block.get(tag_name) - - def parse(self, namespace): - self._namespace = namespace - for node in namespace.nodes[:]: - self._parse_node(node) - del self._namespace - - # Boring parsing boilerplate. - - def _parse_node(self, node): - if isinstance(node, Function): - self._parse_function(node) - elif isinstance(node, Enum): - self._parse_enum(node) - elif isinstance(node, Bitfield): - self._parse_bitfield(node) - elif isinstance(node, Class): - self._parse_class(node) - elif isinstance(node, Interface): - self._parse_interface(node) - elif isinstance(node, Callback): - self._parse_callback(node) - elif isinstance(node, Record): - self._parse_record(node) - elif isinstance(node, Union): - self._parse_union(node) - elif isinstance(node, GLibBoxed): - self._parse_boxed(node) - - def _parse_class(self, class_): - block = self._blocks.get(class_.type_name) - self._parse_node_common(class_, block) - self._parse_constructors(class_.constructors) - self._parse_methods(class_, class_.methods) - self._parse_vfuncs(class_, class_.virtual_methods) - self._parse_methods(class_, class_.static_methods) - self._parse_properties(class_, class_.properties) - self._parse_signals(class_, class_.signals) - self._parse_fields(class_, class_.fields) - if block: - class_.doc = block.comment - self._parse_type_instance_tags(class_, block) - - def _parse_interface(self, interface): - block = self._blocks.get(interface.type_name) - self._parse_node_common(interface, block) - self._parse_methods(interface, interface.methods) - self._parse_vfuncs(interface, interface.virtual_methods) - self._parse_properties(interface, interface.properties) - self._parse_signals(interface, interface.signals) - self._parse_fields(interface, interface.fields) - if block: - interface.doc = block.comment - - def _parse_record(self, record): - block = self._blocks.get(record.symbol) - self._parse_node_common(record, block) - self._parse_constructors(record.constructors) - self._parse_methods(record, record.methods) - self._parse_fields(record, record.fields, block) - if block: - record.doc = block.comment - - def _parse_boxed(self, boxed): - block = self._blocks.get(boxed.name) - self._parse_node_common(boxed, block) - self._parse_constructors(boxed.constructors) - self._parse_methods(boxed, boxed.methods) - if block: - boxed.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, block) - self._parse_constructors(union.constructors) - self._parse_methods(union, union.methods) - if block: - union.doc = block.comment - - def _parse_enum(self, enum): - block = self._blocks.get(enum.symbol) - self._parse_node_common(enum, block) - if block: - enum.doc = block.comment - type_opt = block.options.get(OPT_TYPE) - if type_opt and type_opt.one() == OPT_VAL_BITFIELD: - # This is hack, but hey, it works :-) - enum.__class__ = Bitfield - - def _parse_bitfield(self, bitfield): - block = self._blocks.get(bitfield.symbol) - self._parse_node_common(bitfield, block) - if block: - bitfield.doc = block.comment - - def _parse_constructors(self, constructors): - for ctor in constructors: - self._parse_function(ctor) - - def _parse_fields(self, parent, fields, block=None): - for field in fields: - self._parse_field(parent, field, block) - - def _parse_properties(self, parent, properties): - for prop in properties: - self._parse_property(parent, prop) - - def _parse_methods(self, parent, methods): - for method in methods: - self._parse_method(parent, method) - - def _parse_vfuncs(self, parent, vfuncs): - for vfunc in vfuncs: - self._parse_vfunc(parent, vfunc) - - def _parse_signals(self, parent, signals): - for signal in signals: - self._parse_signal(parent, signal) - - def _parse_property(self, parent, prop): - block = self._blocks.get('%s:%s' % (parent.type_name, prop.name)) - self._parse_node_common(prop, block) - if block: - prop.doc = block.comment - - transfer_tag = self._get_tag(block, TAG_TRANSFER) - if transfer_tag is not None: - options = {OPT_TRANSFER: Option(transfer_tag.value)} - else: - options = {} - prop.transfer = self._extract_transfer(parent, prop, options) - - type_tag = self._get_tag(block, TAG_TYPE) - if type_tag: - prop.type = self._resolve(type_tag.value, prop.type) - - def _parse_callback(self, callback): - block = self._blocks.get(callback.ctype) - self._parse_node_common(callback, block) - self._parse_params(callback, callback.parameters, block) - self._parse_return(callback, callback.retval, block) - if block: - callback.doc = block.comment - - 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: - callable.doc = block.comment - - def _parse_function(self, func): - block = self._blocks.get(func.symbol) - self._parse_callable(func, block) - self._parse_rename_to_func(func, block) - - def _parse_signal(self, parent, signal): - block = self._blocks.get('%s::%s' % (parent.type_name, signal.name)) - self._parse_node_common(signal, 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 - if block and len(block.tags) > len(signal.parameters): - names = block.tags.items() - else: - names = [] - for i, param in enumerate(signal.parameters): - if names: - name, tag = names[i+1] - param.name = name - options = getattr(tag, 'options', {}) - param_type = options.get(OPT_TYPE) - if param_type: - param.type = self._resolve(param_type.one(), param.type) - else: - tag = None - self._parse_param(signal, param, tag) - self._parse_return(signal, signal.retval, block) - if block: - signal.doc = block.comment - - def _parse_method(self, parent, meth): - block = self._blocks.get(meth.symbol) - self._parse_function(meth) - virtual = self._get_tag(block, TAG_VFUNC) - if virtual: - invoker_name = virtual.value - matched = False - for vfunc in parent.virtual_methods: - if vfunc.name == invoker_name: - matched = True - vfunc.invoker = meth - break - if not matched: - print "warning: unmatched virtual invoker %r for method %r" % \ - (invoker_name, meth.symbol) - - def _parse_vfunc(self, parent, vfunc): - key = '%s::%s' % (parent.type_name, vfunc.name) - self._parse_callable(vfunc, self._blocks.get(key)) - if vfunc.invoker: - # We normally expect annotations like (element-type) to be - # applied to the invoker. - block = self._blocks.get(vfunc.invoker.symbol) - self._parse_callable(vfunc, block) - - 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 _check_arg_annotations(self, parent, params, block): - if block is None: - return - for tag in block.tags.keys(): - if tag == TAG_RETURNS: - continue - for param in params: - if param.name == tag: - break - else: - return - #print 'WARNING: annotation for "%s" refers to unknown ' \ - # 'argument "%s"' % (parent.name, tag) - - def _parse_params(self, parent, params, block): - self._check_arg_annotations(parent, params, block) - for param in params: - tag = self._get_tag(block, param.name) - self._parse_param(parent, param, tag) - - def _parse_return(self, parent, return_, block): - tag = self._get_tag(block, TAG_RETURNS) - self._parse_param_ret_common(parent, return_, tag) - - def _get_parameter_index(self, parent, param_name, location_name): - index = parent.get_parameter_index(param_name) - if index is None: - raise InvalidAnnotationError( - "can't find parameter %s referenced by parameter %s of %r" - % (param_name, location_name, parent.name)) - - return index - - def _parse_param(self, parent, param, tag): - options = getattr(tag, 'options', {}) - if isinstance(parent, Function): - scope = options.get(OPT_SCOPE) - if scope: - param.scope = scope.one() - if param.scope not in [PARAM_SCOPE_CALL, - PARAM_SCOPE_ASYNC, - PARAM_SCOPE_NOTIFIED]: - raise InvalidAnnotationError( - "scope for %s of %r is invalid (%r), must be one of " - "call, async, notified." - % (param.name, parent.name, param.scope)) - 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 = self._get_parameter_index(parent, - destroy.one(), - param.name) - self._fixup_param_destroy(parent, param) - closure = options.get(OPT_CLOSURE) - if closure: - param.closure_index = self._get_parameter_index(parent, - closure.one(), - param.name) - self._fixup_param_closure(parent, param) - if isinstance(parent, Callback): - if OPT_CLOSURE in options: - # For callbacks, (closure) appears without an - # argument, and tags a parameter that is a closure. We - # represent it (weirdly) in the gir and typelib by - # setting param.closure_index to its own index. - param.closure_index = parent.get_parameter_index(param.name) - self._fixup_param_closure(parent, param) - - self._parse_param_ret_common(parent, param, tag) - - def _fixup_param_destroy(self, parent, param): - for p in parent.parameters: - if p is not param and p.destroy_index == param.destroy_index: - p.destroy_index = -1 - - def _fixup_param_closure(self, parent, param): - for p in parent.parameters: - if p is not param and p.closure_index == param.closure_index: - p.closure_index = -1 - - def _parse_param_ret_common(self, parent, node, tag): - options = getattr(tag, 'options', {}) - (node.direction, node.caller_allocates) = \ - self._extract_direction(node, options) - container_type = self._extract_container_type( - parent, node, options) - if container_type is not None: - node.type = container_type - if node.direction is None: - node.direction = self._guess_direction(node) - node.caller_allocates = False - node.transfer = self._extract_transfer(parent, node, options) - param_type = options.get(OPT_TYPE) - if param_type: - node.type = self._resolve(param_type.one(), node.type) - - if (OPT_ALLOW_NONE in options or - node.type.ctype == 'GCancellable*'): - node.allow_none = True - - assert node.transfer is not None - if tag is not None and tag.comment is not None: - node.doc = tag.comment - - for key in options: - if '.' in key: - value = options.get(key) - if value: - node.attributes.append((key, value.one())) - - def _extract_direction(self, node, options): - caller_allocates = False - if (OPT_INOUT in options or - OPT_INOUT_ALT in options): - direction = PARAM_DIRECTION_INOUT - elif OPT_OUT in options: - subtype = options[OPT_OUT] - if subtype is not None: - subtype = subtype.one() - direction = PARAM_DIRECTION_OUT - if subtype in (None, ''): - if (node.type.name not in BASIC_GIR_TYPES) and node.type.ctype: - caller_allocates = '**' not in node.type.ctype - else: - caller_allocates = False - elif subtype == 'caller-allocates': - caller_allocates = True - elif subtype == 'callee-allocates': - caller_allocates = False - else: - raise InvalidAnnotationError( - "out direction for %s is invalid (%r)" % (node, subtype)) - elif OPT_IN in options: - direction = PARAM_DIRECTION_IN - else: - direction = node.direction - return (direction, caller_allocates) - - def _guess_array(self, node): - ctype = node.type.ctype - if ctype is None: - return False - if not ctype.endswith('*'): - return False - if node.type.canonical in default_array_types: - return True - return False - - def _is_array_type(self, node): - if node.type.name in ['GLib.Array', 'GLib.PtrArray', - 'GLib.ByteArray']: - return True - if node.type.ctype in ['GArray*', 'GPtrArray*', 'GByteArray*']: - return True - return False - - def _extract_container_type(self, parent, node, options): - has_element_type = OPT_ELEMENT_TYPE in options - has_array = OPT_ARRAY in options - - if not has_array: - has_array = self._is_array_type(node) - - # FIXME: This is a hack :-( - if (not isinstance(node, Field) and - (not has_element_type and - (node.direction is None - or isinstance(node, Return) - or node.direction == PARAM_DIRECTION_IN))): - if self._guess_array(node): - has_array = True - - if has_array: - container_type = self._parse_array(parent, node, options) - elif has_element_type: - container_type = self._parse_element_type(parent, node, options) - else: - container_type = None - - return container_type - - def _parse_array(self, parent, node, options): - array_opt = options.get(OPT_ARRAY) - if array_opt: - array_values = array_opt.all() - else: - array_values = {} - - is_g_array = self._is_array_type(node) - - element_type = options.get(OPT_ELEMENT_TYPE) - if element_type is not None: - element_type_node = self._resolve(element_type.one()) - else: - if is_g_array: - element_type_node = None - else: - element_type_node = Type(node.type.name) # erase ctype - - if is_g_array: - type_name = node.type.name - else: - type_name = None - - container_type = Array(type_name, node.type.ctype, - 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( - OPT_ARRAY_ZERO_TERMINATED) == '1' - length = array_values.get(OPT_ARRAY_LENGTH) - if length is not None: - param_index = self._get_parameter_index(parent, length, node.name) - container_type.length_param_index = param_index - # For in parameters we're incorrectly deferring - # char/unsigned char to utf8 when a length annotation - # is specified. - if (isinstance(node, Parameter) and - node.type.name == 'utf8' and - self._guess_direction(node) == PARAM_DIRECTION_IN and - element_type is None): - # FIXME: unsigned char/guchar should be uint8 - container_type.element_type = Type('gint8') - 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'] or - node.type.ctype in ['GList*', 'GSList*']): - assert len(element_type) == 1 - container_type = List( - node.type.name, - node.type.ctype, - self._resolve(element_type[0])) - elif (node.type.name in ['GLib.HashTable'] or - node.type.ctype in ['GHashTable*']): - assert len(element_type) == 2 - container_type = Map( - node.type.name, - node.type.ctype, - self._resolve(element_type[0]), - self._resolve(element_type[1])) - elif self._is_array_type(node): - container_type = Array(node.type.name, - node.type.ctype, - self._resolve(element_type[0])) - else: - print 'FIXME: unhandled element-type container:', node - return container_type - - def _extract_transfer(self, parent, node, options): - transfer_opt = options.get(OPT_TRANSFER) - if transfer_opt is None: - transfer = self._guess_transfer(node, options) - else: - transfer = transfer_opt.one() - if transfer is None: - transfer = PARAM_TRANSFER_FULL - if transfer not in [PARAM_TRANSFER_NONE, - PARAM_TRANSFER_CONTAINER, - PARAM_TRANSFER_FULL]: - raise InvalidAnnotationError( - "transfer for %s of %r is invalid (%r), must be one of " - "none, container, full." % (node, parent.name, transfer)) - return transfer - - def _parse_node_common(self, node, block): - self._parse_version(node, block) - self._parse_deprecated(node, block) - self._parse_attributes(node, block) - self._parse_skip(node, block) - self._parse_foreign(node, block) - - def _parse_version(self, node, block): - since_tag = self._get_tag(block, TAG_SINCE) - if since_tag is None: - return - node.version = since_tag.value - - def _parse_deprecated(self, node, block): - deprecated_tag = self._get_tag(block, TAG_DEPRECATED) - if deprecated_tag is None: - return - value = deprecated_tag.value - if ': ' in value: - version, desc = value.split(': ') - else: - desc = value - version = None - node.deprecated = desc - if version is not None: - node.deprecated_version = version - - def _parse_attributes(self, node, block): - annos_tag = self._get_tag(block, TAG_ATTRIBUTES) - if annos_tag is None: - return - options = AnnotationParser.parse_options(annos_tag.value) - for key, value in options.iteritems(): - if value: - 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_foreign(self, node, block): - if block is not None: - if OPT_FOREIGN in block.options: - node.foreign = True - - def _parse_type_instance_tags(self, node, block): - tag = self._get_tag(block, TAG_UNREF_FUNC) - node.unref_func = tag.value if tag else None - tag = self._get_tag(block, TAG_REF_FUNC) - node.ref_func = tag.value if tag else None - tag = self._get_tag(block, TAG_SET_VALUE_FUNC) - node.set_value_func = tag.value if tag else None - tag = self._get_tag(block, TAG_GET_VALUE_FUNC) - node.get_value_func = tag.value if tag else None - - def _parse_rename_to_func(self, node, block): - rename_to_tag = self._get_tag(block, TAG_RENAME_TO) - if rename_to_tag is None: - return - new_name = rename_to_tag.value - - shadowed = [] - - def shadowed_filter(n): - if isinstance(n, Function) and n.symbol == new_name: - shadowed.append(n) - return False - return True - - self._namespace.remove_matching(shadowed_filter) - if len(shadowed) == 1: - # method override; use the same (stripped) name as the overloaded - # method referenced. - # Note that 'g_timeout_add_full' may specify a new_name of - # 'g_timeout_add' but the *real* name desired is the stripped name - # of 'g_timeout_add' which is 'timeout_add' (for example). - node.name = shadowed[0].name - elif len(shadowed) == 0: - # literal rename, to force a particular prefix strip or whatever - # Example: the "nm-utils" package uses a "NM" prefix in most places - # but some functions have an "nm_utils_" prefix; the 'Rename To:' - # annotation in this case is used to strip the 'utils_' part off. - node.name = new_name - else: - assert False # more than two shadowed methods? Shouldn't happen. - - def _guess_direction(self, node): - if node.direction: - return node.direction - is_pointer = False - if node.type.ctype: - is_pointer = '*' in node.type.ctype - - if is_pointer and node.type.name in BASIC_GIR_TYPES: - return PARAM_DIRECTION_OUT - - return PARAM_DIRECTION_IN - - def _guess_transfer(self, node, options): - if node.transfer is not None: - return node.transfer - - # Anything with 'const' gets none - if node.type.is_const: - return PARAM_TRANSFER_NONE - elif node.type.name in [TYPE_NONE, TYPE_ANY]: - return PARAM_TRANSFER_NONE - elif isinstance(node.type, Varargs): - return PARAM_TRANSFER_NONE - elif isinstance(node, Parameter): - if node.direction in [PARAM_DIRECTION_INOUT, - PARAM_DIRECTION_OUT]: - if node.caller_allocates: - return PARAM_TRANSFER_NONE - return PARAM_TRANSFER_FULL - # This one is a hack for compatibility; the transfer - # for string parameters really has no defined meaning. - elif node.type.canonical == 'utf8': - return PARAM_TRANSFER_FULL - else: - return PARAM_TRANSFER_NONE - elif isinstance(node, Return): - if (isinstance(node.type, Array) and - node.type.element_type is not None and - node.type.element_type.name == 'utf8'): - return PARAM_TRANSFER_FULL - elif (node.type.canonical in BASIC_GIR_TYPES or - (node.type.canonical in [TYPE_NONE, TYPE_ANY] and - node.type.is_const)): - return PARAM_TRANSFER_NONE - else: - return PARAM_TRANSFER_FULL - elif isinstance(node, Field): - return PARAM_TRANSFER_NONE - elif isinstance(node, Property): - return PARAM_TRANSFER_NONE - else: - raise AssertionError(node) diff --git a/giscanner/ast.py b/giscanner/ast.py index 8c7ea093..3bb6cf82 100644 --- a/giscanner/ast.py +++ b/giscanner/ast.py @@ -19,63 +19,173 @@ # Boston, MA 02111-1307, USA. # -"""AST nodes -This file descbribes abstract data type nodes independent on the -implementation language. - -These can later on be extended (eg subclassed) with additional information -which is language/library/domain specific. +from .odict import odict +from .utils import to_underscores + +class Type(object): + """A Type can be either: +* A reference to a node (target_giname) +* A reference to a "fundamental" type like 'utf8' +* A "foreign" type - this can be any string." +If none are specified, then it's in an "unresolved" state. +In this case, the ctype must be specified. """ + def __init__(self, + ctype=None, + target_fundamental=None, + target_giname=None, + target_foreign=None, + _target_unknown=False, + is_const=False, + origin_symbol=None): + self.ctype = ctype + self.origin_symbol = origin_symbol + if _target_unknown: + assert isinstance(self, TypeUnknown) + elif target_fundamental: + assert target_giname is None + assert target_foreign is None + elif target_giname: + assert '.' in target_giname + assert target_fundamental is None + assert target_foreign is None + elif target_foreign: + assert ctype is not None + assert target_giname is None + assert target_fundamental is None + else: + assert ctype is not None + self.target_fundamental = target_fundamental + self.target_giname = target_giname + self.target_foreign = target_foreign + self.is_const = is_const + + @property + def resolved(self): + return (self.target_fundamental or + self.target_giname or + self.target_foreign) + + def get_giname(self): + assert self.target_giname is not None + return self.target_giname.split('.')[1] + + def __cmp__(self, other): + if self.target_fundamental: + return cmp(self.target_fundamental, other.target_fundamental) + if self.target_giname: + return cmp(self.target_giname, other.target_giname) + if self.target_foreign: + return cmp(self.target_foreign, other.target_foreign) + return cmp(self.ctype, other.ctype) + + def is_equiv(self, typeval): + """Return True if the specified types are compatible at + an introspection level, disregarding their C types. + A sequence may be given for typeval, in which case + this function returns True if the type is compatible with + any.""" + if isinstance(typeval, (list, tuple)): + for val in typeval: + if self.is_equiv(val): + return True + return False + return self == typeval + + def clone(self): + return Type(target_fundamental=self.target_fundamental, + target_giname=self.target_giname, + target_foreign=self.target_foreign, + ctype=self.ctype, + is_const=self.is_const) + + def __str__(self): + if self.target_fundamental: + return self.target_fundamental + elif self.target_giname: + return self.target_giname + elif self.target_foreign: + return self.target_foreign + + def __repr__(self): + if self.target_fundamental: + data = 'target_fundamental=%s, ' % (self.target_fundamental, ) + elif self.target_giname: + data = 'target_giname=%s, ' % (self.target_giname, ) + elif self.target_foreign: + data = 'target_foreign=%s, ' % (self.target_foreign, ) + else: + data = '' + return '%s(%sctype=%s)' % (self.__class__.__name__, data, self.ctype) + +class TypeUnknown(Type): + def __init__(self): + Type.__init__(self, _target_unknown=True) + ###### ## Fundamental types ###### # Two special ones -TYPE_NONE = 'none' -TYPE_ANY = 'gpointer' +TYPE_NONE = Type(target_fundamental='none', ctype='void') +TYPE_ANY = Type(target_fundamental='gpointer', ctype='gpointer') # "Basic" types -TYPE_BOOLEAN = 'gboolean' -TYPE_INT8 = 'gint8' -TYPE_UINT8 = 'guint8' -TYPE_INT16 = 'gint16' -TYPE_UINT16 = 'guint16' -TYPE_INT32 = 'gint32' -TYPE_UINT32 = 'guint32' -TYPE_INT64 = 'gint64' -TYPE_UINT64 = 'guint64' -TYPE_CHAR = 'gchar' -TYPE_SHORT = 'gshort' -TYPE_USHORT = 'gushort' -TYPE_INT = 'gint' -TYPE_UINT = 'guint' -TYPE_LONG = 'glong' -TYPE_ULONG = 'gulong' +TYPE_BOOLEAN = Type(target_fundamental='gboolean', ctype='gboolean') +TYPE_INT8 = Type(target_fundamental='gint8', ctype='gint8') +TYPE_UINT8 = Type(target_fundamental='guint8', ctype='guint8') +TYPE_INT16 = Type(target_fundamental='gint16', ctype='gint16') +TYPE_UINT16 = Type(target_fundamental='guint16', ctype='guint16') +TYPE_INT32 = Type(target_fundamental='gint32', ctype='gint32') +TYPE_UINT32 = Type(target_fundamental='guint32', ctype='guint32') +TYPE_INT64 = Type(target_fundamental='gint64', ctype='gint64') +TYPE_UINT64 = Type(target_fundamental='guint64', ctype='guint64') +TYPE_CHAR = Type(target_fundamental='gchar', ctype='gchar') +TYPE_SHORT = Type(target_fundamental='gshort', ctype='gshort') +TYPE_USHORT = Type(target_fundamental='gushort', ctype='gushort') +TYPE_INT = Type(target_fundamental='gint', ctype='gint') +TYPE_UINT = Type(target_fundamental='guint', ctype='guint') +TYPE_LONG = Type(target_fundamental='glong', ctype='glong') +TYPE_ULONG = Type(target_fundamental='gulong', ctype='gulong') # C99 types -TYPE_LONG_LONG = 'long long' -TYPE_LONG_ULONG = 'unsigned long long' -TYPE_FLOAT = 'gfloat' -TYPE_DOUBLE = 'gdouble' +TYPE_LONG_LONG = Type(target_fundamental='long long', ctype='long long') +TYPE_LONG_ULONG = Type(target_fundamental='unsigned long long', + ctype='unsigned long long') +TYPE_FLOAT = Type(target_fundamental='gfloat', ctype='gfloat') +TYPE_DOUBLE = Type(target_fundamental='gdouble', ctype='gdouble') # ? -TYPE_LONG_DOUBLE = 'long double' +TYPE_LONG_DOUBLE = Type(target_fundamental='long double', + ctype='long double') +TYPE_UNICHAR = Type(target_fundamental='gunichar', ctype='gunichar') # C types with semantics overlaid -TYPE_GTYPE = 'GType' -TYPE_STRING = 'utf8' -TYPE_FILENAME = 'filename' +TYPE_GTYPE = Type(target_fundamental='GType', ctype='GType') +TYPE_STRING = Type(target_fundamental='utf8', ctype='gchar*') +TYPE_FILENAME = Type(target_fundamental='filename', ctype='gchar*') + +TYPE_VALIST = Type(target_fundamental='va_list', ctype='va_list') BASIC_GIR_TYPES = [TYPE_BOOLEAN, TYPE_INT8, TYPE_UINT8, TYPE_INT16, TYPE_UINT16, TYPE_INT32, TYPE_UINT32, TYPE_INT64, TYPE_UINT64, TYPE_CHAR, TYPE_SHORT, TYPE_USHORT, TYPE_INT, TYPE_UINT, TYPE_LONG, TYPE_ULONG, TYPE_LONG_LONG, TYPE_LONG_ULONG, TYPE_FLOAT, TYPE_DOUBLE, - TYPE_LONG_DOUBLE, TYPE_GTYPE] + TYPE_LONG_DOUBLE, TYPE_UNICHAR, TYPE_GTYPE] GIR_TYPES = [TYPE_NONE, TYPE_ANY] GIR_TYPES.extend(BASIC_GIR_TYPES) -GIR_TYPES.extend([TYPE_STRING, TYPE_FILENAME]) +GIR_TYPES.extend([TYPE_STRING, TYPE_FILENAME, TYPE_VALIST]) + +INTROSPECTABLE_BASIC = list(GIR_TYPES) +for v in [TYPE_NONE, TYPE_ANY, + TYPE_LONG_LONG, TYPE_LONG_ULONG, + TYPE_LONG_DOUBLE, TYPE_VALIST]: + INTROSPECTABLE_BASIC.remove(v) type_names = {} for typeval in GIR_TYPES: - type_names[typeval] = typeval + type_names[typeval.target_fundamental] = typeval +basic_type_names = {} +for typeval in BASIC_GIR_TYPES: + basic_type_names[typeval.target_fundamental] = typeval # C builtin type_names['char'] = TYPE_CHAR @@ -106,13 +216,12 @@ type_names['signed long long'] = TYPE_LONG_LONG type_names['guchar'] = TYPE_UINT8 type_names['gchararray'] = TYPE_STRING type_names['gchar*'] = TYPE_STRING +type_names['goffset'] = TYPE_INT64 +type_names['gunichar2'] = TYPE_UINT16 type_names['gsize'] = TYPE_ULONG type_names['gssize'] = TYPE_LONG type_names['gconstpointer'] = TYPE_ANY -# Some special C types that aren't scriptable, and we just squash -type_names['va_list'] = TYPE_ANY - # C stdio, used in GLib public headers; squash this for now here # until we move scanning into GLib and can (skip) type_names['FILE*'] = TYPE_ANY @@ -142,19 +251,6 @@ type_names['ssize_t'] = TYPE_LONG # Obj-C type_names['id'] = TYPE_ANY -# These types, when seen by reference, are converted into an Array() -# by default -default_array_types = {} -default_array_types['guint8*'] = TYPE_UINT8 -default_array_types['guchar*'] = TYPE_UINT8 -default_array_types['utf8*'] = TYPE_STRING -default_array_types['char**'] = TYPE_STRING -default_array_types['gchar**'] = TYPE_STRING - -# These types, when seen by reference, are interpreted as out parameters -default_out_types = (TYPE_SHORT, TYPE_USHORT, TYPE_INT, TYPE_UINT, - TYPE_LONG, TYPE_ULONG, TYPE_FLOAT, TYPE_DOUBLE) - ## ## Parameters ## @@ -171,24 +267,189 @@ PARAM_TRANSFER_NONE = 'none' PARAM_TRANSFER_CONTAINER = 'container' PARAM_TRANSFER_FULL = 'full' -def type_name_from_ctype(ctype): - return type_names.get(ctype, ctype) +class Namespace(object): + def __init__(self, name, version, + identifier_prefixes=None, + symbol_prefixes=None): + self.name = name + self.version = version + if identifier_prefixes: + self.identifier_prefixes = identifier_prefixes + else: + self.identifier_prefixes = [name] + if symbol_prefixes: + self.symbol_prefixes = symbol_prefixes + else: + ps = self.identifier_prefixes + self.symbol_prefixes = [to_underscores(p).lower() for p in ps] + self._names = odict() # Maps from GIName -> node + self._aliases = {} # Maps from GIName -> GIName + self._type_names = {} # Maps from GTName -> node + self._ctypes = {} # Maps from CType -> node + self._symbols = {} # Maps from function symbols -> Function + + @property + def names(self): + return self._names + + @property + def aliases(self): + return self._aliases + + @property + def type_names(self): + return self._type_names + + @property + def ctypes(self): + return self._ctypes + + def type_from_name(self, name, ctype=None): + """Backwards compatibility method for older .gir files, which +only use the 'name' attribute. If name refers to a fundamental type, +create a Type object referncing it. If name is already a +fully-qualified GIName like 'Foo.Bar', returns a Type targeting it . +Otherwise a Type targeting name qualififed with the namespace name is +returned.""" + if name in type_names: + return Type(target_fundamental=name, ctype=ctype) + if '.' in name: + target = name + else: + target = '%s.%s' % (self.name, name) + return Type(target_giname=target, ctype=ctype) + + def contains_ident(self, ident): + """Return True if this namespace should contain the given C +identifier string.""" + return any(ident.startswith(prefix) for prefix in self.identifier_prefixes) + + def append(self, node, replace=False): + previous = self._names.get(node.name) + if previous is not None: + if not replace: + raise ValueError("Namespace conflict") + self.remove(previous) + # A layering violation...but oh well. + from .glibast import GLibBoxed + if isinstance(node, Alias): + self._aliases[node.name] = node + elif isinstance(node, (GLibBoxed, Interface, Class)): + self._type_names[node.type_name] = node + elif isinstance(node, Function): + self._symbols[node.symbol] = node + assert isinstance(node, Node) + assert node.namespace is None + node.namespace = self + self._names[node.name] = node + if hasattr(node, 'ctype'): + self._ctypes[node.ctype] = node + if hasattr(node, 'symbol'): + self._ctypes[node.symbol] = node + + def remove(self, node): + from .glibast import GLibBoxed + if isinstance(node, Alias): + del self._aliases[node.name] + elif isinstance(node, (GLibBoxed, Interface, Class)): + del self._type_names[node.type_name] + del self._names[node.name] + node.namespace = None + if hasattr(node, 'ctype'): + del self._ctypes[node.ctype] + if isinstance(node, Function): + del self._symbols[node.symbol] + + def float(self, node): + """Like remove(), but doesn't unset the node's namespace +back-reference, and it's still possible to look up +functions via get_by_symbol().""" + if isinstance(node, Function): + symbol = node.symbol + self.remove(node) + self._symbols[symbol] = node + node.namespace = self + + def __iter__(self): + return iter(self._names) + + def iteritems(self): + return self._names.iteritems() + + def itervalues(self): + return self._names.itervalues() + + def get(self, name): + return self._names.get(name) + + def get_by_ctype(self, ctype): + return self._ctypes.get(ctype) + + def get_by_symbol(self, symbol): + return self._symbols.get(symbol) + + def walk(self, callback): + for node in self.itervalues(): + node.walk(callback, []) + +class Include(object): + def __init__(self, name, version): + self.name = name + self.version = version -class Node(object): + @classmethod + def from_string(cls, string): + return cls(*string.split('-', 1)) - def __init__(self, name=None): - self.name = name + def __cmp__(self, other): + namecmp = cmp(self.name, other.name) + if namecmp != 0: + return namecmp + return cmp(self.version, other.version) + + def __hash__(self): + return hash(str(self)) + + def __str__(self): + return '%s-%s' % (self.name, self.version) + +class Annotated(object): + """An object which has a few generic metadata +properties.""" + def __init__(self): + self.version = None self.skip = False self.introspectable = True self.attributes = [] # (key, value)* self.deprecated = None self.deprecated_version = None - self.version = None + self.doc = None + +class Node(Annotated): + """A node is a type of object which is uniquely identified by its +(namespace, name) pair. When combined with a ., this is called a +GIName. It's possible for nodes to contain or point to other nodes.""" + + c_name = property(lambda self: self.namespace.name + self.name) + gi_name = property(lambda self: '%s.%s' % (self.namespace.name, self.name)) + + def __init__(self, name=None): + Annotated.__init__(self) + self.namespace = None # Should be set later by Namespace.append() + self.name = name self.foreign = False self.file_positions = set() + def create_type(self): + """Create a Type object referencing this node.""" + assert self.namespace is not None + return Type(target_giname=('%s.%s' % (self.namespace.name, self.name))) + def __cmp__(self, other): + nscmp = cmp(self.namespace, other.namespace) + if nscmp != 0: + return nscmp return cmp(self.name, other.name) def __repr__(self): @@ -207,49 +468,17 @@ class Node(object): if symbol.source_filename: self.add_file_position(symbol.source_filename, symbol.line, -1) -class Namespace(Node): - - def __init__(self, name, version): - Node.__init__(self, name) - self.version = version - self.nodes = [] - - def __repr__(self): - return '%s(%r, %r, %r)' % (self.__class__.__name__, self.name, - self.version, self.nodes) - - def remove_matching(self, pred): - - def recursive_pred(node): - node.remove_matching_children(pred) - return pred(node) - - self.nodes = filter(recursive_pred, self.nodes) - -class Include(Node): - - def __init__(self, name, version): - Node.__init__(self, 'include') - self.name = name - self.version = version - - @classmethod - def from_string(self, string): - return Include(*string.split('-', 1)) - - def __cmp__(self, other): - if not isinstance(other, Include): - return cmp(self, other) - namecmp = cmp(self.name, other.name) - if namecmp != 0: - return namecmp - return cmp(self.version, other.version) - - def __hash__(self): - return hash((self.name, self.version)) + def walk(self, callback, chain): + res = callback(self, chain) + assert res in (True, False), "Walk function must return boolean, not %r" % (res, ) + if not res: + return False + chain.append(self) + self._walk(callback, chain) + chain.pop() - def __str__(self): - return '%s-%s' % (self.name, self.version) + def _walk(self, callback, chain): + pass class Callable(Node): @@ -258,30 +487,29 @@ class Callable(Node): self.retval = retval self.parameters = parameters self.throws = not not throws - self.doc = None - - def __repr__(self): - return '%s(%r, %r, %r)' % (self.__class__.__name__, - self.name, self.retval, - self.parameters) - -class Function(Callable): - - def __init__(self, name, retval, parameters, symbol, throws=None): - Callable.__init__(self, name, retval, parameters, throws) - self.symbol = symbol - self.is_method = False - self.doc = None def get_parameter_index(self, name): for i, parameter in enumerate(self.parameters): - if parameter.name == name: - return i + int(self.is_method) + if parameter.argname == name: + return i + raise ValueError("Unknown argument %s" % (name, )) def get_parameter(self, name): for parameter in self.parameters: - if parameter.name == name: + if parameter.argname == name: return parameter + raise ValueError("Unknown argument %s" % (name, )) + + +class Function(Callable): + + def __init__(self, name, retval, parameters, throws, symbol): + Callable.__init__(self, name, retval, parameters, throws) + self.symbol = symbol + self.is_method = False + self.is_constructor = False + self.shadowed_by = None # C symbol string + self.shadows = None # C symbol string class VFunction(Callable): @@ -297,59 +525,72 @@ class VFunction(Callable): return obj -class Type(Node): - - def __init__(self, name, ctype=None): - Node.__init__(self, name) - self.ctype = ctype - self.resolved = False - self.is_const = False - self.canonical = None - self.derefed_canonical = None - class Varargs(Type): def __init__(self): - Type.__init__(self, '') + Type.__init__(self, '', target_fundamental='') class Array(Type): - - def __init__(self, name, ctype, element_type): - if name is None: - name = '' - Type.__init__(self, name, ctype) + C = '' + GLIB_ARRAY = 'GLib.Array' + GLIB_BYTEARRAY = 'GLib.ByteArray' + GLIB_PTRARRAY = 'GLib.PtrArray' + + def __init__(self, array_type, element_type, **kwargs): + Type.__init__(self, target_fundamental='', + **kwargs) + if (array_type is None or array_type == self.C): + self.array_type = self.C + else: + assert array_type in (self.GLIB_ARRAY, + self.GLIB_BYTEARRAY, + self.GLIB_PTRARRAY) + self.array_type = array_type + assert isinstance(element_type, Type) self.element_type = element_type self.zeroterminated = True - self.length_param_index = -1 self.length_param_name = None self.size = None - def __repr__(self): - return 'Array(%r, %r)' % (self.name, self.element_type, ) - + def clone(self): + arr = Array(self.array_type, self.element_type) + arr.element_type = self.element_type + arr.zeroterminated = self.zeroterminated + arr.length_param_name = self.length_param_name + arr.size = self.size + return arr class List(Type): - def __init__(self, name, ctype, element_type): - Type.__init__(self, name, ctype) + def __init__(self, name, element_type, **kwargs): + Type.__init__(self, target_fundamental='', + **kwargs) + self.name = name + assert isinstance(element_type, Type) self.element_type = element_type - def __repr__(self): - return 'List(%r of %r)' % (self.name, self.element_type, ) - + def clone(self): + l = List(self.name, self.element_type) + l.element_type = self.element_type + l.zeroterminated = self.zeroterminated + l.length_param_name = self.length_param_name + l.size = self.size + return l class Map(Type): - def __init__(self, name, ctype, key_type, value_type): - Type.__init__(self, name, ctype) + def __init__(self, key_type, value_type, **kwargs): + Type.__init__(self, target_fundamental='', **kwargs) + assert isinstance(key_type, Type) self.key_type = key_type + assert isinstance(value_type, Type) self.value_type = value_type - def __repr__(self): - return 'Map(%r <%r,%r>)' % (self.name, self.key_type, self.value_type) - + def clone(self): + m = Map(self.key_type, self.value_type) + return m class Alias(Node): @@ -358,42 +599,43 @@ class Alias(Node): self.target = target self.ctype = ctype - def __repr__(self): - return 'Alias(%r, %r)' % (self.name, self.target) - -class TypeContainer(Node): +class TypeContainer(Annotated): + """A fundamental base class for Return and Parameter.""" - def __init__(self, name, typenode, transfer): - Node.__init__(self, name) + def __init__(self, typenode, transfer): + Annotated.__init__(self) self.type = typenode - if transfer in [PARAM_TRANSFER_NONE, PARAM_TRANSFER_CONTAINER, - PARAM_TRANSFER_FULL]: + if transfer is not None: self.transfer = transfer + elif typenode.is_const: + self.transfer = PARAM_TRANSFER_NONE else: self.transfer = None class Parameter(TypeContainer): - - def __init__(self, name, typenode, direction=None, - transfer=None, allow_none=False, scope=None): - TypeContainer.__init__(self, name, typenode, transfer) - if direction in [PARAM_DIRECTION_IN, PARAM_DIRECTION_OUT, - PARAM_DIRECTION_INOUT, None]: - self.direction = direction - else: - self.direction = PARAM_DIRECTION_IN - - self.caller_allocates = False + """An argument to a function.""" + + def __init__(self, argname, typenode, direction=None, + transfer=None, allow_none=False, scope=None, + caller_allocates=False): + TypeContainer.__init__(self, typenode, transfer) + self.argname = argname + self.direction = direction self.allow_none = allow_none self.scope = scope - self.closure_index = -1 - self.destroy_index = -1 - self.doc = None + self.caller_allocates = caller_allocates + self.closure_name = None + self.destroy_name = None - def __repr__(self): - return 'Parameter(%r, %r)' % (self.name, self.type) + +class Return(TypeContainer): + """A return value from a function.""" + + def __init__(self, rtype, transfer=None): + TypeContainer.__init__(self, rtype, transfer) + self.direction = PARAM_DIRECTION_OUT class Enum(Node): @@ -402,10 +644,6 @@ class Enum(Node): Node.__init__(self, name) self.symbol = symbol self.members = members - self.doc = None - - def __repr__(self): - return 'Enum(%r, %r)' % (self.name, self.members) class Bitfield(Node): @@ -414,21 +652,18 @@ class Bitfield(Node): Node.__init__(self, name) self.symbol = symbol self.members = members - self.doc = None - - def __repr__(self): - return 'Bitfield(%r, %r)' % (self.name, self.members) -class Member(Node): +class Member(Annotated): def __init__(self, name, value, symbol): - Node.__init__(self, name) + Annotated.__init__(self) + self.name = name self.value = value self.symbol = symbol - def __repr__(self): - return 'Member(%r, %r)' % (self.name, self.value) + def __cmp__(self, other): + return cmp(self.name, other.name) class Record(Node): @@ -439,44 +674,41 @@ class Record(Node): self.constructors = [] self.symbol = symbol self.disguised = disguised - self.doc = None self.methods = [] + self.static_methods = [] + + def _walk(self, callback, chain): + for ctor in self.constructors: + ctor.walk(callback, chain) + for func in self.methods: + func.walk(callback, chain) + for func in self.static_methods: + func.walk(callback, chain) + for field in self.fields: + if field.anonymous_node is not None: + field.anonymous_node.walk(callback, chain) def remove_matching_children(self, pred): self.fields = filter(pred, self.fields) self.constructors = filter(pred, self.constructors) self.methods = filter(pred, self.methods) -# BW compat, remove -Struct = Record +class Field(Annotated): -class Field(Node): - - def __init__(self, name, typenode, symbol, readable, writable, bits=None): - Node.__init__(self, name) + def __init__(self, name, typenode, readable, writable, bits=None, + anonymous_node=None): + Annotated.__init__(self) + assert (typenode or anonymous_node) + self.name = name self.type = typenode - self.symbol = symbol self.readable = readable self.writable = writable self.bits = bits + self.anonymous_node = anonymous_node - def __repr__(self): - if self.bits: - return 'Field(%r, %r, %r)' % (self.name, self.type, self.bits) - else: - return 'Field(%r, %r)' % (self.name, self.type) - - -class Return(TypeContainer): - - def __init__(self, rtype, transfer=None): - TypeContainer.__init__(self, None, rtype, transfer) - self.direction = PARAM_DIRECTION_OUT - self.doc = None - - def __repr__(self): - return 'Return(%r)' % (self.type, ) + def __cmp__(self, other): + return cmp(self.name, other.name) class Class(Node): @@ -484,7 +716,12 @@ class Class(Node): def __init__(self, name, parent, is_abstract): Node.__init__(self, name) self.ctype = name + self.c_symbol_prefix = None self.parent = parent + # When we're in the scanner, we keep around a list + # of parents so that we can transparently fall back + # if there are 'hidden' parents + self.parent_chain = [] self.glib_type_struct = None self.is_abstract = is_abstract self.methods = [] @@ -494,7 +731,6 @@ class Class(Node): self.constructors = [] self.properties = [] self.fields = [] - self.doc = None def remove_matching_children(self, pred): self.methods = filter(pred, self.methods) @@ -502,87 +738,71 @@ class Class(Node): self.properties = filter(pred, self.properties) self.fields = filter(pred, self.fields) - def __repr__(self): - return '%s(%r, %r, %r)' % ( - self.__class__.__name__, - self.name, self.parent, self.methods) - + def _walk(self, callback, chain): + for meth in self.methods: + meth.walk(callback, chain) + for meth in self.virtual_methods: + meth.walk(callback, chain) + for meth in self.static_methods: + meth.walk(callback, chain) + for ctor in self.constructors: + ctor.walk(callback, chain) + for field in self.fields: + if field.anonymous_node: + field.anonymous_node.walk(callback, chain) class Interface(Node): def __init__(self, name, parent): Node.__init__(self, name) + self.c_symbol_prefix = None self.parent = parent + self.parent_chain = [] self.methods = [] + self.static_methods = [] self.virtual_methods = [] self.glib_type_struct = None self.properties = [] self.fields = [] self.prerequisites = [] - self.doc = None - - def __repr__(self): - return '%s(%r, %r)' % ( - self.__class__.__name__, - self.name, self.methods) + def _walk(self, callback, chain): + for meth in self.methods: + meth.walk(callback, chain) + for meth in self.static_methods: + meth.walk(callback, chain) + for meth in self.virtual_methods: + meth.walk(callback, chain) + for field in self.fields: + if field.anonymous_node: + field.anonymous_node.walk(callback, chain) class Constant(Node): - def __init__(self, name, type_name, value): + def __init__(self, name, value_type, value): Node.__init__(self, name) - self.type = Type(type_name) + self.value_type = value_type self.value = value - def __repr__(self): - return 'Constant(%r, %r, %r)' % ( - self.name, self.type, self.value) +class Property(Node): -class Property(TypeContainer): - - def __init__(self, name, type_name, readable, writable, - construct, construct_only, ctype=None, transfer=None): - self.type = Type(type_name, ctype) - TypeContainer.__init__(self, name, self.type, transfer) + def __init__(self, name, typeobj, readable, writable, + construct, construct_only, transfer=None): + Node.__init__(self, name) + self.type = typeobj self.readable = readable self.writable = writable self.construct = construct self.construct_only = construct_only - self.doc = None - - def __repr__(self): - return '%s(%r, %r)' % ( - self.__class__.__name__, - self.name, self.type) + self.transfer = PARAM_TRANSFER_NONE -# FIXME: Inherit from Function +class Callback(Callable): - -class Callback(Node): - - def __init__(self, name, retval, parameters, ctype=None): - Node.__init__(self, name) - self.retval = retval - self.parameters = parameters + def __init__(self, name, retval, parameters, throws, ctype=None): + Callable.__init__(self, name, retval, parameters, throws) self.ctype = ctype - self.throws = False - self.doc = None - - def get_parameter_index(self, name): - for i, parameter in enumerate(self.parameters): - if parameter.name == name: - return i - - def get_parameter(self, name): - for parameter in self.parameters: - if parameter.name == name: - return parameter - - def __repr__(self): - return 'Callback(%r, %r, %r)' % ( - self.name, self.retval, self.parameters) class Union(Node): @@ -592,8 +812,16 @@ class Union(Node): self.fields = [] self.constructors = [] self.methods = [] + self.static_methods = [] self.symbol = symbol - self.doc = None - def __repr__(self): - return 'Union(%r, %r)' % (self.name, self.fields, ) + def _walk(self, callback, chain): + for ctor in self.constructors: + ctor.walk(callback, chain) + for meth in self.methods: + meth.walk(callback, chain) + for meth in self.static_methods: + meth.walk(callback, chain) + for field in self.fields: + if field.anonymous_node: + field.anonymous_node.walk(callback, chain) diff --git a/giscanner/codegen.py b/giscanner/codegen.py new file mode 100644 index 00000000..0c0c559b --- /dev/null +++ b/giscanner/codegen.py @@ -0,0 +1,137 @@ +# -*- Mode: Python -*- +# GObject-Introspection - a framework for introspecting GObject libraries +# Copyright (C) 2010 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from contextlib import contextmanager +from . import ast + +class CCodeGenerator(object): + def __init__(self, namespace, out_h_filename, out_c_filename): + self.out_h_filename = out_h_filename + self.out_c_filename = out_c_filename + self._function_bodies = {} + self.namespace = namespace + + def gen_symbol(self, name): + name = name.replace(' ', '_') + return '%s_%s' % (self.namespace.symbol_prefixes[0], name) + + def _typecontainer_to_ctype(self, param): + if (isinstance(param, ast.Parameter) and + param.direction in (ast.PARAM_DIRECTION_OUT, + ast.PARAM_DIRECTION_INOUT)): + suffix = '*' + else: + suffix = '' + if (param.type.is_equiv((ast.TYPE_STRING, ast.TYPE_FILENAME)) and + param.transfer == ast.PARAM_TRANSFER_NONE): + return "const gchar*" + suffix + return param.type.ctype + suffix + + def _write_prelude(self, out, func): + out.write(""" +%s +%s (""" % (self._typecontainer_to_ctype(func.retval), func.symbol)) + l = len(func.parameters) + if func.parameters: + for i, param in enumerate(func.parameters): + ctype = self._typecontainer_to_ctype(param) + out.write('%s %s' % (ctype, param.argname)) + if i < l - 1: + out.write(", ") + else: + out.write('void') + out.write(")") + + def _write_prototype(self, func): + self._write_prelude(self.out_h, func) + self.out_h.write(";\n\n") + + def _write_annotation_transfer(self, transfer): + self.out_c.write("(transfer %s)" % (transfer, )) + + def _write_docs(self, func): + self.out_c.write("/**\n * %s:\n" % (func.symbol, )) + for param in func.parameters: + self.out_c.write(" * @%s: " % (param.argname, )) + if param.direction in (ast.PARAM_DIRECTION_OUT, + ast.PARAM_DIRECTION_INOUT): + if param.caller_allocates: + allocate_string = ' caller-allocates' + else: + allocate_string = '' + self.out_c.write("(%s%s) " % (param.direction, + allocate_string)) + self._write_annotation_transfer(param.transfer) + self.out_c.write(":\n") + self.out_c.write(' *\n') + self.out_c.write(' * Undocumented.\n') + self.out_c.write(' *\n') + self.out_c.write(' * Returns: ') + self._write_annotation_transfer(func.retval.transfer) + self.out_c.write('\n */') + + @contextmanager + def _function(self, func): + self._write_prototype(func) + self._write_docs(func) + self._write_prelude(self.out_c, func) + self.out_c.write("\n{\n") + yield + self.out_c.write("}\n\n") + + def _codegen_start(self): + warning = '/* GENERATED BY testcodegen.py; DO NOT EDIT */\n\n' + self.out_h.write(warning) + nsupper = self.namespace.name.upper() + self.out_h.write(""" +#ifndef __%s_H__ +#define __%s_H__ + +#include +""" % (nsupper, nsupper)) + + self.out_c.write(warning) + self.out_c.write("""#include "%s"\n\n""" % (self.out_h_filename, )) + + def _codegen_end(self): + self.out_h.write("""#endif\n""") + + self.out_h.close() + self.out_c.close() + + def set_function_body(self, node, body): + assert isinstance(node, ast.Function) + self._function_bodies[node] = body + + def codegen(self): + self.out_h = open(self.out_h_filename, 'w') + self.out_c = open(self.out_c_filename, 'w') + + self._codegen_start() + + for node in self.namespace.itervalues(): + if isinstance(node, ast.Function): + with self._function(node): + body = self._function_bodies.get(node) + if not body: + body = '' + self.out_c.write(body) + + self._codegen_end() diff --git a/giscanner/dumper.py b/giscanner/dumper.py index c30822b5..96f29371 100644 --- a/giscanner/dumper.py +++ b/giscanner/dumper.py @@ -23,7 +23,7 @@ import os import subprocess import tempfile -from .glibtransformer import IntrospectionBinary +from .gdumpparser import IntrospectionBinary from .utils import get_libtool_command # bugzilla.gnome.org/558436 diff --git a/giscanner/gdumpparser.py b/giscanner/gdumpparser.py new file mode 100644 index 00000000..ce270720 --- /dev/null +++ b/giscanner/gdumpparser.py @@ -0,0 +1,455 @@ +# -*- Mode: Python -*- +# GObject-Introspection - a framework for introspecting GObject libraries +# Copyright (C) 2008 Johan Dahlin +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +import os +import sys +import tempfile +import shutil +import subprocess +from xml.etree.cElementTree import parse + +from . import ast +from . import glibast + +# GParamFlags +G_PARAM_READABLE = 1 << 0 +G_PARAM_WRITABLE = 1 << 1 +G_PARAM_CONSTRUCT = 1 << 2 +G_PARAM_CONSTRUCT_ONLY = 1 << 3 +G_PARAM_LAX_VALIDATION = 1 << 4 +G_PARAM_STATIC_NAME = 1 << 5 +G_PARAM_STATIC_NICK = 1 << 6 +G_PARAM_STATIC_BLURB = 1 << 7 + + +class IntrospectionBinary(object): + + def __init__(self, args, tmpdir=None): + self.args = args + if tmpdir is None: + self.tmpdir = tempfile.mkdtemp('', 'tmp-introspect') + else: + self.tmpdir = tmpdir + + +class Unresolved(object): + + def __init__(self, target): + self.target = target + + +class UnknownTypeError(Exception): + pass + + +class GDumpParser(object): + + def __init__(self, transformer): + self._transformer = transformer + self._namespace = transformer.namespace + self._binary = None + self._get_type_functions = [] + self._gtype_data = {} + self._boxed_types = {} + self._private_internal_types = {} + + # Public API + + def init_parse(self): + """Do parsing steps that don't involve the introspection binary + + This does enough work that get_type_functions() can be called. + + """ + + self._transformer.parse() + + # First pass: parsing + for node in self._namespace.itervalues(): + if isinstance(node, ast.Function): + self._initparse_function(node) + if self._namespace.name == 'GObject': + for node in self._namespace.itervalues(): + if isinstance(node, ast.Record): + self._initparse_gobject_record(node) + + def get_get_type_functions(self): + return self._get_type_functions + + def set_introspection_binary(self, binary): + self._binary = binary + + def parse(self): + """Do remaining parsing steps requiring introspection binary""" + + # Get all the GObject data by passing our list of get_type + # functions to the compiled binary, returning an XML blob. + tree = self._execute_binary_get_tree() + root = tree.getroot() + for child in root: + self._gtype_data[child.attrib['name']] = child + for child in root: + self._introspect_type(child) + + # Pair up boxed types and class records + for name, boxed in self._boxed_types.iteritems(): + self._pair_boxed_type(boxed) + for node in self._namespace.itervalues(): + if isinstance(node, (ast.Class, ast.Interface)): + self._find_class_record(node) + + # Clear the _get_type functions out of the namespace; + # Anyone who wants them can get them from the ast.Class/Interface/Boxed + to_remove = [] + for name, node in self._namespace.iteritems(): + if isinstance(node, (ast.Class, ast.Interface, glibast.GLibBoxed, + glibast.GLibEnum, glibast.GLibFlags)): + get_type_name = node.get_type + if get_type_name == 'intern': + continue + assert get_type_name, node + (ns, name) = self._transformer.split_csymbol(get_type_name) + assert ns is self._namespace + get_type_func = self._namespace.get(name) + assert get_type_func, name + to_remove.append(get_type_func) + for node in to_remove: + self._namespace.remove(node) + + # Helper functions + + def _execute_binary_get_tree(self): + """Load the library (or executable), returning an XML +blob containing data gleaned from GObject's primitive introspection.""" + in_path = os.path.join(self._binary.tmpdir, 'types.txt') + f = open(in_path, 'w') + # TODO: Introspect GQuark functions + for func in self._get_type_functions: + f.write(func) + f.write('\n') + f.close() + out_path = os.path.join(self._binary.tmpdir, 'dump.xml') + + args = [] + args.extend(self._binary.args) + args.append('--introspect-dump=%s,%s' % (in_path, out_path)) + + # Invoke the binary, having written our get_type functions to types.txt + try: + try: + subprocess.check_call(args, stdout=sys.stdout, stderr=sys.stderr) + except subprocess.CalledProcessError, e: + # Clean up temporaries + raise SystemExit(e) + return parse(out_path) + finally: + shutil.rmtree(self._binary.tmpdir) + + def _create_gobject(self, node): + type_name = 'G' + node.name + if type_name == 'GObject': + parent_gitype = None + symbol = 'intern' + elif type_name == 'GInitiallyUnowned': + parent_gitype = ast.Type(target_giname='GLib.Object') + symbol = 'g_initially_unowned_get_type' + else: + assert False + gnode = glibast.GLibObject(node.name, parent_gitype, type_name, symbol, 'object', True) + if type_name == 'GObject': + gnode.fields.extend(node.fields) + else: + # http://bugzilla.gnome.org/show_bug.cgi?id=569408 + # GInitiallyUnowned is actually a typedef for GObject, but + # that's not reflected in the GIR, where it appears as a + # subclass (as it appears in the GType hierarchy). So + # what we do here is copy all of the GObject fields into + # GInitiallyUnowned so that struct offset computation + # works correctly. + gnode.fields = self._namespace.get('Object').fields + self._namespace.append(gnode, replace=True) + + # Parser + + def _initparse_function(self, func): + symbol = func.symbol + if symbol.startswith('_'): + return + elif symbol.endswith('_get_type'): + self._initparse_get_type_function(func) + + def _initparse_get_type_function(self, func): + if self._namespace.name == 'GLib': + # No GObjects in GLib + return False + if (self._namespace.name == 'GObject' and + func.symbol in ('g_object_get_type', 'g_initially_unowned_get_type')): + # We handle these internally, see _create_gobject + return True + if func.parameters: + return False + # GType *_get_type(void) + rettype = func.retval.type + if not (rettype.is_equiv(ast.TYPE_GTYPE) + or rettype.target_giname == 'Gtk.Type'): + self._transformer.log_warning("function returns '%r', not a GType" + % (func.retval.type, )) + return False + + self._get_type_functions.append(func.symbol) + return True + + def _initparse_gobject_record(self, record): + # Special handling for when we're parsing GObject + internal_names = ("Object", 'InitiallyUnowned') + if record.name in internal_names: + self._create_gobject(record) + return + if record.name == 'InitiallyUnownedClass': + record.fields = self._namespace.get('ObjectClass').fields + self._namespace.append(record, replace=True) + + # Introspection over the data we get from the dynamic + # GObject/GType system out of the binary + + def _introspect_type(self, xmlnode): + if xmlnode.tag in ('enum', 'flags'): + self._introspect_enum(xmlnode) + elif xmlnode.tag == 'class': + self._introspect_object(xmlnode) + elif xmlnode.tag == 'interface': + self._introspect_interface(xmlnode) + elif xmlnode.tag == 'boxed': + self._introspect_boxed(xmlnode) + elif xmlnode.tag == 'fundamental': + self._introspect_fundamental(xmlnode) + else: + raise ValueError("Unhandled introspection XML tag %s", xmlnode.tag) + + def _introspect_enum(self, node): + members = [] + for member in node.findall('member'): + # Keep the name closer to what we'd take from C by default; + # see http://bugzilla.gnome.org/show_bug.cgi?id=575613 + name = member.attrib['nick'].replace('-', '_') + members.append(glibast.GLibEnumMember(name, + member.attrib['value'], + member.attrib['name'], + member.attrib['nick'])) + + klass = (glibast.GLibFlags if node.tag == 'flags' else glibast.GLibEnum) + type_name = node.attrib['name'] + enum_name = self._transformer.strip_identifier_or_warn(type_name, fatal=True) + node = klass(enum_name, type_name, members, node.attrib['get-type']) + self._namespace.append(node, replace=True) + + def _split_type_and_symbol_prefix(self, xmlnode): + """Infer the C symbol prefix from the _get_type function.""" + get_type = xmlnode.attrib['get-type'] + (ns, name) = self._transformer.split_csymbol(get_type) + assert ns is self._namespace + assert name.endswith('_get_type') + return (get_type, name[:-len('_get_type')]) + + def _introspect_object(self, xmlnode): + type_name = xmlnode.attrib['name'] + # We handle this specially above; in 2.16 and below there + # was no g_object_get_type, for later versions we need + # to skip it + if type_name == 'GObject': + return + is_abstract = bool(xmlnode.attrib.get('abstract', False)) + (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode) + node = glibast.GLibObject( + self._transformer.strip_identifier_or_warn(type_name, fatal=True), + None, + type_name, + get_type, c_symbol_prefix, is_abstract) + self._parse_parents(xmlnode, node) + self._introspect_properties(node, xmlnode) + self._introspect_signals(node, xmlnode) + self._introspect_implemented_interfaces(node, xmlnode) + + self._add_record_fields(node) + self._namespace.append(node, replace=True) + + def _introspect_interface(self, xmlnode): + type_name = xmlnode.attrib['name'] + (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode) + node = glibast.GLibInterface( + self._transformer.strip_identifier_or_warn(type_name, fatal=True), + None, + type_name, get_type, c_symbol_prefix) + self._introspect_properties(node, xmlnode) + self._introspect_signals(node, xmlnode) + for child in xmlnode.findall('prerequisite'): + name = child.attrib['name'] + prereq = self._transformer.create_type_from_user_string(name) + node.prerequisites.append(prereq) + # GtkFileChooserEmbed is an example of a private interface, we + # just filter them out + if xmlnode.attrib['get-type'].startswith('_'): + self._private_internal_types[type_name] = node + else: + self._namespace.append(node, replace=True) + + def _introspect_boxed(self, xmlnode): + type_name = xmlnode.attrib['name'] + # This one doesn't go in the main namespace; we associate it with + # the struct or union + (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode) + node = glibast.GLibBoxed(type_name, get_type, c_symbol_prefix) + self._boxed_types[node.type_name] = node + + def _introspect_implemented_interfaces(self, node, xmlnode): + gt_interfaces = [] + for interface in xmlnode.findall('implements'): + gitype = self._transformer.create_type_from_user_string(interface.attrib['name']) + gt_interfaces.append(gitype) + node.interfaces = gt_interfaces + + def _introspect_properties(self, node, xmlnode): + for pspec in xmlnode.findall('property'): + ctype = pspec.attrib['type'] + flags = int(pspec.attrib['flags']) + readable = (flags & G_PARAM_READABLE) != 0 + writable = (flags & G_PARAM_WRITABLE) != 0 + construct = (flags & G_PARAM_CONSTRUCT) != 0 + construct_only = (flags & G_PARAM_CONSTRUCT_ONLY) != 0 + node.properties.append(ast.Property( + pspec.attrib['name'], + self._transformer.create_type_from_user_string(ctype), + readable, writable, construct, construct_only, + ctype, + )) + node.properties = node.properties + + def _introspect_signals(self, node, xmlnode): + for signal_info in xmlnode.findall('signal'): + rctype = signal_info.attrib['return'] + rtype = self._transformer.create_type_from_user_string(rctype) + return_ = ast.Return(rtype) + parameters = [] + for i, parameter in enumerate(signal_info.findall('param')): + if i == 0: + argname = 'object' + else: + argname = 'p%s' % (i-1, ) + pctype = parameter.attrib['type'] + ptype = self._transformer.create_type_from_user_string(pctype) + param = ast.Parameter(argname, ptype) + param.transfer = ast.PARAM_TRANSFER_NONE + parameters.append(param) + signal = glibast.GLibSignal(signal_info.attrib['name'], return_, parameters) + node.signals.append(signal) + node.signals = node.signals + + def _parse_parents(self, xmlnode, node): + if 'parents' in xmlnode.attrib: + parent_types = map(lambda s: self._transformer.create_type_from_user_string(s), + xmlnode.attrib['parents'].split(',')) + else: + parent_types = [] + node.parent_chain = parent_types + + def _introspect_fundamental(self, xmlnode): + # We only care about types that can be instantiatable, other + # fundamental types such as the Clutter.Fixed/CoglFixed registers + # are not yet interesting from an introspection perspective and + # are ignored + if not xmlnode.attrib.get('instantiatable', False): + return + + type_name = xmlnode.attrib['name'] + is_abstract = bool(xmlnode.attrib.get('abstract', False)) + (get_type, c_symbol_prefix) = self._split_type_and_symbol_prefix(xmlnode) + node = glibast.GLibObject( + self._transformer.strip_identifier_or_warn(type_name, fatal=True), + None, + type_name, + get_type, c_symbol_prefix, is_abstract) + self._parse_parents(xmlnode, node) + node.fundamental = True + self._introspect_implemented_interfaces(node, xmlnode) + + self._add_record_fields(node) + self._namespace.append(node, replace=True) + + def _add_record_fields(self, node): + # add record fields + record = self._namespace.get(node.name) + if not isinstance(record, ast.Record): + return + node.fields = record.fields + for field in node.fields: + if isinstance(field, ast.Field): + # Object instance fields are assumed to be read-only + # (see also _find_class_record and transformer.py) + field.writable = False + + def _pair_boxed_type(self, boxed): + name = self._transformer.strip_identifier_or_warn(boxed.type_name, fatal=True) + pair_node = self._namespace.get(name) + if not pair_node: + boxed_item = glibast.GLibBoxedOther(name, boxed.type_name, + boxed.get_type, + boxed.c_symbol_prefix) + elif isinstance(pair_node, ast.Record): + boxed_item = glibast.GLibBoxedStruct(pair_node.name, boxed.type_name, + boxed.get_type, + boxed.c_symbol_prefix) + boxed_item.inherit_file_positions(pair_node) + boxed_item.fields = pair_node.fields + elif isinstance(pair_node, ast.Union): + boxed_item = glibast.GLibBoxedUnion(pair_node.name, boxed.type_name, + boxed.get_type, + boxed.c_symbol_prefix) + boxed_item.inherit_file_positions(pair_node) + boxed_item.fields = pair_node.fields + else: + return False + self._namespace.append(boxed_item, replace=True) + + def _strip_class_suffix(self, name): + if (name.endswith('Class') or + name.endswith('Iface')): + return name[:-5] + elif name.endswith('Interface'): + return name[:-9] + else: + return None + + def _find_class_record(self, cls): + pair_record = None + if isinstance(cls, ast.Class): + pair_record = self._namespace.get(cls.name + 'Class') + else: + for suffix in ('Iface', 'Interface'): + pair_record = self._namespace.get(cls.name + suffix) + if pair_record: + break + if not (pair_record and isinstance(pair_record, ast.Record)): + return + + gclass_struct = glibast.GLibRecord.from_record(pair_record) + self._namespace.append(gclass_struct, replace=True) + cls.glib_type_struct = gclass_struct.create_type() + cls.inherit_file_positions(pair_record) + gclass_struct.is_gtype_struct_for = cls.create_type() diff --git a/giscanner/girparser.py b/giscanner/girparser.py index 9fee1fc1..2fc0a355 100644 --- a/giscanner/girparser.py +++ b/giscanner/girparser.py @@ -22,13 +22,8 @@ import os from xml.etree.cElementTree import parse -from .ast import (Alias, Array, Callback, Constant, Enum, Function, Field, - Namespace, Parameter, Property, Return, Union, Struct, Type, - Varargs, Include) -from .glibast import (GLibEnum, GLibEnumMember, GLibFlags, - GLibInterface, GLibObject, GLibBoxedStruct, - GLibBoxedUnion, GLibBoxedOther) - +from . import ast +from . import glibast from .girwriter import COMPATIBLE_GIR_VERSION CORE_NS = "http://www.gtk.org/introspection/core/1.0" @@ -51,7 +46,6 @@ def _cns(tag): class GIRParser(object): def __init__(self): - self._include_parsing = False self._shared_libraries = [] self._includes = set() self._pkgconfig_packages = set() @@ -72,6 +66,8 @@ class GIRParser(object): self._namespace = None self._shared_libraries = [] self._pkgconfig_packages = set() + self._c_includes = set() + self._c_prefix = None self._parse_api(tree.getroot()) def get_namespace(self): @@ -83,6 +79,12 @@ class GIRParser(object): def get_includes(self): return self._includes + def get_c_includes(self): + return self._c_includes + + def get_c_prefix(self): + return self._c_prefix + def get_pkgconfig_packages(self): if not hasattr(self, '_pkgconfig_packages'): self._pkgconfig_packages = [] @@ -91,11 +93,17 @@ class GIRParser(object): def get_doc(self): return parse(self._filename) - def set_include_parsing(self, include_parsing): - self._include_parsing = include_parsing - # Private + def _find_first_child(self, node, name): + for child in node.getchildren(): + if child.tag == name: + return child + return None + + def _find_children(self, node, name): + return [child for child in node.getchildren() if child.tag == name] + def _get_current_file(self): if not self._filename_stack: return None @@ -105,9 +113,6 @@ class GIRParser(object): return curfile[len(cwd):] return curfile - def _add_node(self, node): - self._namespace.nodes.append(node) - def _parse_api(self, root): assert root.tag == _corens('repository') version = root.attrib['version'] @@ -121,11 +126,21 @@ class GIRParser(object): self._parse_include(node) elif node.tag == _corens('package'): self._parse_pkgconfig_package(node) + elif node.tag == _cns('include'): + self._parse_c_include(node) ns = root.find(_corens('namespace')) assert ns is not None - self._namespace = Namespace(ns.attrib['name'], - ns.attrib['version']) + identifier_prefixes = ns.attrib.get(_cns('identifier-prefixes')) + if identifier_prefixes: + identifier_prefixes = identifier_prefixes.split(',') + symbol_prefixes = ns.attrib.get(_cns('symbol-prefixes')) + if symbol_prefixes: + symbol_prefixes = symbol_prefixes.split(',') + self._namespace = ast.Namespace(ns.attrib['name'], + ns.attrib['version'], + identifier_prefixes=identifier_prefixes, + symbol_prefixes=symbol_prefixes) if 'shared-library' in ns.attrib: self._shared_libraries.extend( ns.attrib['shared-library'].split(',')) @@ -141,7 +156,7 @@ class GIRParser(object): _corens('interface'): self._parse_object_interface, _corens('record'): self._parse_record, _corens('union'): self._parse_union, - _corens('boxed'): self._parse_boxed, + _glibns('boxed'): self._parse_boxed, } for node in ns.getchildren(): @@ -150,31 +165,54 @@ class GIRParser(object): method(node) def _parse_include(self, node): - include = Include(node.attrib['name'], + include = ast.Include(node.attrib['name'], node.attrib['version']) self._includes.add(include) def _parse_pkgconfig_package(self, node): - if not hasattr(self, '_pkgconfig_packages'): - self._pkgconfig_packages = [] self._pkgconfig_packages.add(node.attrib['name']) + def _parse_c_include(self, node): + self._c_includes.add(node.attrib['name']) + def _parse_alias(self, node): typeval = self._parse_type(node) - alias = Alias(node.attrib['name'], + alias = ast.Alias(node.attrib['name'], typeval, node.attrib.get(_cns('type'))) - self._add_node(alias) + self._namespace.append(alias) + + def _parse_generic_attribs(self, node, obj): + assert isinstance(obj, ast.Annotated) + doc = node.find(_corens('doc')) + if doc is not None: + obj.doc = doc.text + version = node.attrib.get('version') + if version: + obj.version = version + deprecated = node.attrib.get('deprecated') + if deprecated: + obj.deprecated = deprecated + introspectable = node.attrib.get('introspectable') + if introspectable: + obj.introspectable = int(introspectable) > 0 def _parse_object_interface(self, node): + parent = node.attrib.get('parent') + if parent: + parent_type = self._namespace.type_from_name(parent) + else: + parent_type = None + ctor_args = [node.attrib['name'], - node.attrib.get('parent'), + parent_type, node.attrib[_glibns('type-name')], - node.attrib[_glibns('get-type')]] + node.attrib[_glibns('get-type')], + node.attrib.get(_cns('symbol-prefix'))] if node.tag == _corens('interface'): - klass = GLibInterface + klass = glibast.GLibInterface elif node.tag == _corens('class'): - klass = GLibObject + klass = glibast.GLibObject is_abstract = node.attrib.get('abstract') is_abstract = is_abstract and is_abstract != '0' ctor_args.append(is_abstract) @@ -182,38 +220,49 @@ class GIRParser(object): raise AssertionError(node) obj = klass(*ctor_args) - self._add_node(obj) + self._parse_generic_attribs(node, obj) + type_struct = node.attrib.get(_glibns('type-struct')) + if type_struct: + obj.glib_type_struct = self._namespace.type_from_name(type_struct) + self._namespace.append(obj) - if self._include_parsing: - return ctor_args.append(node.attrib.get(_cns('type'))) - for iface in node.findall(_corens('implements')): - obj.interfaces.append(iface.attrib['name']) - for iface in node.findall(_corens('prerequisites')): - obj.prerequisities.append(iface.attrib['name']) - for method in node.findall(_corens('method')): - func = self._parse_function_common(method, Function) + for iface in self._find_children(node, _corens('implements')): + obj.interfaces.append(self._namespace.type_from_name(iface.attrib['name'])) + for iface in self._find_children(node, _corens('prerequisite')): + obj.prerequisites.append(self._namespace.type_from_name(iface.attrib['name'])) + for func_node in self._find_children(node, _corens('function')): + func = self._parse_function_common(func_node, ast.Function) + obj.static_methods.append(func) + for method in self._find_children(node, _corens('method')): + func = self._parse_function_common(method, ast.Function) func.is_method = True obj.methods.append(func) - for ctor in node.findall(_corens('constructor')): - obj.constructors.append( - self._parse_function_common(ctor, Function)) - for callback in node.findall(_corens('callback')): - obj.fields.append(self._parse_function_common(callback, Callback)) - for field in node.findall(_corens('field')): - obj.fields.append(self._parse_field(field)) - for prop in node.findall(_corens('property')): + for method in self._find_children(node, _corens('virtual-method')): + func = self._parse_function_common(method, ast.VFunction) + self._parse_generic_attribs(method, func) + func.is_method = True + func.invoker = method.get('invoker') + obj.virtual_methods.append(func) + for ctor in self._find_children(node, _corens('constructor')): + func = self._parse_function_common(ctor, ast.Function) + func.is_constructor = True + obj.constructors.append(func) + obj.fields.extend(self._parse_fields(node)) + for prop in self._find_children(node, _corens('property')): obj.properties.append(self._parse_property(prop)) - for signal in node.findall(_glibns('signal')): - obj.signals.append(self._parse_function_common(signal, Function)) + for signal in self._find_children(node, _glibns('signal')): + obj.signals.append(self._parse_function_common(signal, ast.Function)) def _parse_callback(self, node): - callback = self._parse_function_common(node, Callback) - self._add_node(callback) + callback = self._parse_function_common(node, ast.Callback) + self._namespace.append(callback) def _parse_function(self, node): - function = self._parse_function_common(node, Function) - self._add_node(function) + function = self._parse_function_common(node, ast.Function) + function.shadows = node.attrib.get('shadows', None) + function.shadowed_by = node.attrib.get('shadowed-by', None) + self._namespace.append(function) def _parse_function_common(self, node, klass): name = node.attrib['name'] @@ -221,187 +270,297 @@ class GIRParser(object): if not returnnode: raise ValueError('node %r has no return-value' % (name, )) transfer = returnnode.attrib.get('transfer-ownership') - retval = Return(self._parse_type(returnnode), transfer) + retval = ast.Return(self._parse_type(returnnode), transfer) + self._parse_generic_attribs(returnnode, retval) parameters = [] - if klass is Callback: - func = klass(name, retval, parameters, + throws = (node.attrib.get('throws') == '1') + + if klass is ast.Callback: + func = klass(name, retval, parameters, throws, node.attrib.get(_cns('type'))) - else: + elif klass is ast.Function: identifier = node.attrib.get(_cns('identifier')) - throws = (node.attrib.get('throws') == '1') - func = klass(name, retval, parameters, identifier, throws) - - if self._include_parsing: - return func + func = klass(name, retval, parameters, throws, identifier) + elif klass is ast.VFunction: + func = klass(name, retval, parameters, throws) + else: + assert False parameters_node = node.find(_corens('parameters')) if (parameters_node is not None): - for paramnode in parameters_node.findall(_corens('parameter')): - param = Parameter(paramnode.attrib.get('name'), - self._parse_type(paramnode), - paramnode.attrib.get('direction'), + for paramnode in self._find_children(parameters_node, _corens('parameter')): + typeval = self._parse_type(paramnode) + param = ast.Parameter(paramnode.attrib.get('name'), + typeval, + paramnode.attrib.get('direction') or ast.PARAM_DIRECTION_IN, paramnode.attrib.get('transfer-ownership'), - paramnode.attrib.get('allow-none') == '1') + paramnode.attrib.get('allow-none') == '1', + paramnode.attrib.get('scope'), + paramnode.attrib.get('caller-allocates') == '1') + self._parse_generic_attribs(paramnode, param) parameters.append(param) + for i, paramnode in enumerate(self._find_children(parameters_node, + _corens('parameter'))): + param = parameters[i] + self._parse_type_second_pass(func, paramnode, param.type) + closure = paramnode.attrib.get('closure') + if closure: + idx = int(closure) + assert idx < len(parameters), "%d >= %d" % (idx, len(parameters)) + param.closure_name = parameters[idx].argname + destroy = paramnode.attrib.get('destroy') + if destroy: + idx = int(destroy) + assert idx < len(parameters), "%d >= %d" % (idx, len(parameters)) + param.destroy_name = parameters[idx].argname + + self._parse_type_second_pass(func, returnnode, retval.type) + + self._parse_generic_attribs(node, func) return func - def _parse_record(self, node): + def _parse_fields(self, node): + res = [] + names = (_corens('field'), _corens('record'), _corens('union'), _corens('callback')) + for child in node.getchildren(): + if child.tag in names: + fieldobj = self._parse_field(child) + res.append(fieldobj) + return res + + def _parse_record(self, node, anonymous=False): if _glibns('type-name') in node.attrib: - struct = GLibBoxedStruct(node.attrib['name'], + struct = glibast.GLibBoxedStruct(node.attrib['name'], node.attrib[_glibns('type-name')], node.attrib[_glibns('get-type')], + node.attrib.get(_cns('symbol-prefix')), node.attrib.get(_cns('type'))) + elif _glibns('is-gtype-struct-for') in node.attrib: + struct = glibast.GLibRecord(node.attrib['name'], + node.attrib.get(_cns('type')), + disguised=node.attrib.get('disguised') == '1') + is_gtype_struct_for = node.attrib[_glibns('is-gtype-struct-for')] + struct.is_gtype_struct_for = self._namespace.type_from_name(is_gtype_struct_for) else: - disguised = node.attrib.get('disguised') == '1' - struct = Struct(node.attrib['name'], + struct = ast.Record(node.attrib.get('name'), node.attrib.get(_cns('type')), - disguised=disguised) - self._add_node(struct) - - if self._include_parsing: - return - for field in node.findall(_corens('field')): - struct.fields.append(self._parse_field(field)) - for callback in node.findall(_corens('callback')): - struct.fields.append( - self._parse_function_common(callback, Callback)) - for method in node.findall(_corens('method')): - struct.fields.append( - self._parse_function_common(method, Function)) - for ctor in node.findall(_corens('constructor')): + disguised=node.attrib.get('disguised') == '1') + if node.attrib.get('foreign') == '1': + struct.foreign = True + self._parse_generic_attribs(node, struct) + if not anonymous: + self._namespace.append(struct) + + struct.fields.extend(self._parse_fields(node)) + for method in self._find_children(node, _corens('method')): + struct.methods.append( + self._parse_function_common(method, ast.Function)) + for func in self._find_children(node, _corens('function')): + struct.static_methods.append( + self._parse_function_common(func, ast.Function)) + for ctor in self._find_children(node, _corens('constructor')): struct.constructors.append( - self._parse_function_common(ctor, Function)) + self._parse_function_common(ctor, ast.Function)) + return struct - def _parse_union(self, node): + def _parse_union(self, node, anonymous=False): if _glibns('type-name') in node.attrib: - union = GLibBoxedUnion(node.attrib['name'], + union = glibast.GLibBoxedUnion(node.attrib['name'], node.attrib[_glibns('type-name')], node.attrib[_glibns('get-type')], + node.attrib.get(_cns('symbol-prefix')), node.attrib.get(_cns('type'))) else: - union = Union(node.attrib['name'], + union = ast.Union(node.attrib.get('name'), node.attrib.get(_cns('type'))) - self._add_node(union) + if not anonymous: + self._namespace.append(union) - if self._include_parsing: - return - for callback in node.findall(_corens('callback')): + for callback in self._find_children(node, _corens('callback')): union.fields.append( - self._parse_function_common(callback, Callback)) - for field in node.findall(_corens('field')): - union.fields.append(self._parse_field(field)) - for method in node.findall(_corens('method')): - union.fields.append( - self._parse_function_common(method, Function)) - for ctor in node.findall(_corens('constructor')): + self._parse_function_common(callback, ast.Callback)) + union.fields.extend(self._parse_fields(node)) + for method in self._find_children(node, _corens('method')): + union.methods.append( + self._parse_function_common(method, ast.Function)) + for func in self._find_children(node, _corens('function')): + union.static_methods.append( + self._parse_function_common(func, ast.Function)) + for ctor in self._find_children(node, _corens('constructor')): union.constructors.append( - self._parse_function_common(ctor, Function)) - - def _parse_type(self, node): - typenode = node.find(_corens('type')) - if typenode is not None: - return Type(typenode.attrib['name'], - typenode.attrib.get(_cns('type'))) - - typenode = node.find(_corens('array')) - if typenode is not None: - - array_type = typenode.attrib.get(_cns('type')) - if array_type.startswith('GArray*') or \ - array_type.startswith('GPtrArray*') or \ - array_type.startswith('GByteArray*'): - element_type = None - else: - element_type = self._parse_type(typenode) - - ret = Array(None, array_type, element_type) + self._parse_function_common(ctor, ast.Function)) + return union + + def _parse_type_simple(self, typenode): + # ast.Fields can contain inline callbacks + if typenode.tag == _corens('callback'): + typeval = self._namespace.type_from_name(typenode.attrib['name']) + typeval.ctype = typenode.attrib.get(_cns('type')) + return typeval + # ast.Arrays have their own toplevel XML + elif typenode.tag == _corens('array'): + array_type = typenode.attrib.get('name') + element_type = self._parse_type(typenode) + array_ctype = typenode.attrib.get(_cns('type')) + ret = ast.Array(array_type, element_type, ctype=array_ctype) + # zero-terminated defaults to true... + zero = typenode.attrib.get('zero-terminated') + if zero and zero == '0': + ret.zeroterminated = False + fixed_size = typenode.attrib.get('fixed-size') + if fixed_size: + ret.size = int(fixed_size) - lenidx = typenode.attrib.get('length') - if lenidx: - ret.length_param_index = int(lenidx) return ret + elif typenode.tag == _corens('varargs'): + return ast.Varargs() + elif typenode.tag == _corens('type'): + name = typenode.attrib.get('name') + ctype = typenode.attrib.get(_cns('type')) + if name is None: + if ctype is None: + return ast.TypeUnknown() + return ast.Type(ctype=ctype) + elif name in ['GLib.List', 'GLib.SList']: + subchild = self._find_first_child(typenode, _corens('type')) + if subchild is not None: + element_type = self._parse_type(typenode) + else: + element_type = ast.TYPE_ANY + return ast.List(name, element_type, ctype=ctype) + elif name == 'GLib.HashTable': + subchildren = self._find_children(typenode, _corens('type')) + subchildren_types = map(self._parse_type_simple, subchildren) + while len(subchildren_types) < 2: + subchildren_types.append(ast.TYPE_ANY) + return ast.Map(subchildren_types[0], + subchildren_types[1], + ctype=ctype) + else: + return self._namespace.type_from_name(name, ctype) + else: + assert False, "Failed to parse inner type" - typenode = node.find(_corens('varargs')) - if typenode is not None: - return Varargs() - - raise ValueError("Couldn't parse type of node %r; children=%r", - node, list(node)) + def _parse_type(self, node): + for name in map(_corens, ('callback', 'array', 'varargs', 'type')): + typenode = node.find(name) + if typenode is not None: + return self._parse_type_simple(typenode) + assert False, "Failed to parse toplevel type" + + def _parse_type_second_pass(self, parent, node, typeval): + """A hack necessary to handle the integer parameter indexes on + array types.""" + typenode = node.find(_corens('array')) + if typenode is None: + return + lenidx = typenode.attrib.get('length') + if lenidx is not None: + idx = int(lenidx) + assert idx < len(parent.parameters), "%r %d >= %d" \ + % (parent, idx, len(parent.parameters)) + typeval.length_param_name = parent.parameters[idx].argname def _parse_boxed(self, node): - obj = GLibBoxedOther(node.attrib[_glibns('name')], + obj = glibast.GLibBoxedOther(node.attrib[_glibns('name')], node.attrib[_glibns('type-name')], - node.attrib[_glibns('get-type')]) - self._add_node(obj) - if self._include_parsing: - return - for method in node.findall(_corens('method')): - func = self._parse_function_common(method, Function) + node.attrib[_glibns('get-type')], + node.attrib.get(_cns('symbol-prefix'))) + self._parse_generic_attribs(node, obj) + self._namespace.append(obj) + for method in self._find_children(node, _corens('method')): + func = self._parse_function_common(method, ast.Function) func.is_method = True obj.methods.append(func) - for ctor in node.findall(_corens('constructor')): + for ctor in self._find_children(node, _corens('constructor')): obj.constructors.append( - self._parse_function_common(ctor, Function)) - for callback in node.findall(_corens('callback')): + self._parse_function_common(ctor, ast.Function)) + for callback in self._find_children(node, _corens('callback')): obj.fields.append( - self._parse_function_common(callback, Callback)) + self._parse_function_common(callback, ast.Callback)) def _parse_field(self, node): - type_node = self._parse_type(node) - return Field(node.attrib['name'], - type_node, - type_node.ctype, - node.attrib.get('readable') != '0', - node.attrib.get('writable') == '1', - node.attrib.get('bits')) + type_node = None + anonymous_node = None + if node.tag in map(_corens, ('record', 'union')): + anonymous_elt = node + else: + anonymous_elt = self._find_first_child(node, _corens('callback')) + if anonymous_elt is not None: + if anonymous_elt.tag == _corens('callback'): + anonymous_node = self._parse_function_common(anonymous_elt, ast.Callback) + elif anonymous_elt.tag == _corens('record'): + anonymous_node = self._parse_record(anonymous_elt, anonymous=True) + elif anonymous_elt.tag == _corens('union'): + anonymous_node = self._parse_union(anonymous_elt, anonymous=True) + else: + assert False, anonymous_elt.tag + else: + assert node.tag == _corens('field'), node.tag + type_node = self._parse_type(node) + field = ast.Field(node.attrib.get('name'), + type_node, + node.attrib.get('readable') != '0', + node.attrib.get('writable') == '1', + node.attrib.get('bits'), + anonymous_node=anonymous_node) + self._parse_generic_attribs(node, field) + return field def _parse_property(self, node): - type_node = self._parse_type(node) - return Property(node.attrib['name'], - type_node.name, + prop = ast.Property(node.attrib['name'], + self._parse_type(node), node.attrib.get('readable') != '0', node.attrib.get('writable') == '1', node.attrib.get('construct') == '1', - node.attrib.get('construct-only') == '1', - type_node.ctype) + node.attrib.get('construct-only') == '1') + self._parse_generic_attribs(node, prop) + return prop def _parse_member(self, node): - return GLibEnumMember(node.attrib['name'], - node.attrib['value'], - node.attrib.get(_cns('identifier')), - node.attrib.get(_glibns('nick'))) + member = glibast.GLibEnumMember(node.attrib['name'], + node.attrib['value'], + node.attrib.get(_cns('identifier')), + node.attrib.get(_glibns('nick'))) + self._parse_generic_attribs(node, member) + return member def _parse_constant(self, node): type_node = self._parse_type(node) - constant = Constant(node.attrib['name'], - type_node.name, + constant = ast.Constant(node.attrib['name'], + type_node, node.attrib['value']) - self._add_node(constant) + self._parse_generic_attribs(node, constant) + self._namespace.append(constant) def _parse_enumeration_bitfield(self, node): name = node.attrib.get('name') ctype = node.attrib.get(_cns('type')) get_type = node.attrib.get(_glibns('get-type')) type_name = node.attrib.get(_glibns('type-name')) - if get_type: + glib_error_quark = node.attrib.get(_glibns('error-quark')) + if get_type or glib_error_quark: if node.tag == _corens('bitfield'): - klass = GLibFlags + klass = glibast.GLibFlags else: - klass = GLibEnum + klass = glibast.GLibEnum else: - klass = Enum + if node.tag == _corens('bitfield'): + klass = ast.Bitfield + else: + klass = ast.Enum type_name = ctype members = [] - if klass is Enum: + if klass in (ast.Enum, ast.Bitfield): obj = klass(name, type_name, members) else: obj = klass(name, type_name, members, get_type) + obj.error_quark = glib_error_quark obj.ctype = ctype - self._add_node(obj) + self._parse_generic_attribs(node, obj) + self._namespace.append(obj) - if self._include_parsing: - return - for member in node.findall(_corens('member')): + for member in self._find_children(node, _corens('member')): members.append(self._parse_member(member)) diff --git a/giscanner/girwriter.py b/giscanner/girwriter.py index c8103494..f2074df3 100644 --- a/giscanner/girwriter.py +++ b/giscanner/girwriter.py @@ -22,8 +22,8 @@ from __future__ import with_statement from .ast import (Alias, Array, Bitfield, Callback, Class, Constant, Enum, - Function, Interface, List, Map, Member, Struct, Union, - Varargs, Type, TYPE_ANY) + Function, Interface, List, Map, Member, Record, Union, + Varargs, Type) from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember, GLibFlags, GLibObject, GLibInterface, GLibRecord) @@ -31,21 +31,21 @@ from .xmlwriter import XMLWriter # Bump this for *incompatible* changes to the .gir. # Compatible changes we just make inline -COMPATIBLE_GIR_VERSION = '1.1' +COMPATIBLE_GIR_VERSION = '1.2' class GIRWriter(XMLWriter): - def __init__(self, namespace, shlibs, includes, pkgs, c_includes, cprefix): + def __init__(self, namespace, shlibs, includes, pkgs, c_includes): super(GIRWriter, self).__init__() self.write_comment( '''This file was automatically generated from C sources - DO NOT EDIT! To affect the contents of this file, edit the original C definitions, and/or use gtk-doc annotations. ''') self._write_repository(namespace, shlibs, includes, pkgs, - c_includes, cprefix) + c_includes) def _write_repository(self, namespace, shlibs, includes=None, - packages=None, c_includes=None, cprefix=None): + packages=None, c_includes=None): if includes is None: includes = frozenset() if packages is None: @@ -65,7 +65,9 @@ and/or use gtk-doc annotations. ''') self._write_pkgconfig_pkg(pkg) for c_include in sorted(set(c_includes)): self._write_c_include(c_include) - self._write_namespace(namespace, shlibs, cprefix) + self._namespace = namespace + self._write_namespace(namespace, shlibs) + self._namespace = None def _write_include(self, include): attrs = [('name', include.name), ('version', include.version)] @@ -79,11 +81,12 @@ and/or use gtk-doc annotations. ''') attrs = [('name', c_include)] self.write_tag('c:include', attrs) - def _write_namespace(self, namespace, shlibs, cprefix): + def _write_namespace(self, namespace, shlibs): attrs = [('name', namespace.name), ('version', namespace.version), ('shared-library', ','.join(shlibs)), - ('c:prefix', cprefix)] + ('c:identifier-prefixes', ','.join(namespace.identifier_prefixes)), + ('c:symbol-prefixes', ','.join(namespace.symbol_prefixes))] with self.tagcontext('namespace', attrs): # We define a custom sorting function here because # we want aliases to be first. They're a bit @@ -98,7 +101,7 @@ and/or use gtk-doc annotations. ''') return 1 else: return cmp(a, b) - for node in sorted(namespace.nodes, cmp=nscmp): + for node in sorted(namespace.itervalues(), cmp=nscmp): self._write_node(node) def _write_node(self, node): @@ -112,7 +115,7 @@ and/or use gtk-doc annotations. ''') self._write_class(node) elif isinstance(node, Callback): self._write_callback(node) - elif isinstance(node, Struct): + elif isinstance(node, Record): self._write_record(node) elif isinstance(node, Union): self._write_union(node) @@ -167,11 +170,17 @@ and/or use gtk-doc annotations. ''') self._append_throws(callable, attrs) with self.tagcontext(tag_name, attrs): self._write_generic(callable) - self._write_return_type(callable.retval) - self._write_parameters(callable.parameters) + self._write_return_type(callable.retval, parent=callable) + self._write_parameters(callable, callable.parameters) def _write_function(self, func, tag_name='function'): - attrs = [('c:identifier', func.symbol)] + attrs = [] + if hasattr(func, 'symbol'): + attrs.append(('c:identifier', func.symbol)) + if func.shadowed_by: + attrs.append(('shadowed-by', func.shadowed_by)) + elif func.shadows: + attrs.append(('shadows', func.shadows)) self._write_callable(func, tag_name, attrs) def _write_method(self, method): @@ -183,105 +192,100 @@ and/or use gtk-doc annotations. ''') def _write_constructor(self, method): self._write_function(method, tag_name='constructor') - def _write_return_type(self, return_): + def _write_return_type(self, return_, parent=None): if not return_: return - assert return_.transfer is not None, return_ - attrs = [] - attrs.append(('transfer-ownership', return_.transfer)) + if return_.transfer: + attrs.append(('transfer-ownership', return_.transfer)) with self.tagcontext('return-value', attrs): self._write_generic(return_) - self._write_type(return_.type) + self._write_type(return_.type, function=parent) - def _write_parameters(self, parameters): + def _write_parameters(self, parent, parameters): if not parameters: return with self.tagcontext('parameters'): for parameter in parameters: - self._write_parameter(parameter) - - def _write_parameter(self, parameter): - assert parameter.transfer is not None, parameter + self._write_parameter(parent, parameter) + def _write_parameter(self, parent, parameter): attrs = [] - if parameter.name is not None: - attrs.append(('name', parameter.name)) - if parameter.direction != 'in': + if parameter.argname is not None: + attrs.append(('name', parameter.argname)) + if (parameter.direction is not None) and (parameter.direction != 'in'): attrs.append(('direction', parameter.direction)) attrs.append(('caller-allocates', '1' if parameter.caller_allocates else '0')) - attrs.append(('transfer-ownership', - parameter.transfer)) + if parameter.transfer: + attrs.append(('transfer-ownership', + parameter.transfer)) if parameter.allow_none: attrs.append(('allow-none', '1')) if parameter.scope: attrs.append(('scope', parameter.scope)) - if parameter.closure_index >= 0: - attrs.append(('closure', '%d' % parameter.closure_index)) - if parameter.destroy_index >= 0: - attrs.append(('destroy', '%d' % parameter.destroy_index)) + if parameter.closure_name is not None: + idx = parent.get_parameter_index(parameter.closure_name) + attrs.append(('closure', '%d' % (idx, ))) + if parameter.destroy_name is not None: + idx = parent.get_parameter_index(parameter.destroy_name) + attrs.append(('destroy', '%d' % (idx, ))) with self.tagcontext('parameter', attrs): self._write_generic(parameter) - self._write_type(parameter.type) - - def _type_to_string(self, ntype): - if isinstance(ntype, basestring): - return ntype - return ntype.name - - def _write_type(self, ntype, relation=None): - if isinstance(ntype, basestring): - typename = ntype - type_cname = None - else: - typename = ntype.name - type_cname = ntype.ctype + self._write_type(parameter.type, function=parent) + + def _type_to_name(self, typeval): + if not typeval.resolved: + raise AssertionError("Caught unresolved type %r (ctype=%r)" % (typeval, typeval.ctype)) + assert typeval.target_giname is not None + prefix = self._namespace.name + '.' + if typeval.target_giname.startswith(prefix): + return typeval.target_giname[len(prefix):] + return typeval.target_giname + + def _write_type(self, ntype, relation=None, function=None): + assert isinstance(ntype, Type), ntype + attrs = [] + if ntype.ctype: + attrs.append(('c:type', ntype.ctype)) if isinstance(ntype, Varargs): with self.tagcontext('varargs', []): pass - return - if isinstance(ntype, Array): - attrs = [] + elif isinstance(ntype, Array): + if ntype.array_type != Array.C: + attrs.insert(0, ('name', ntype.array_type)) if not ntype.zeroterminated: - attrs.append(('zero-terminated', '0')) - if ntype.length_param_index >= 0: - attrs.append(('length', '%d' % (ntype.length_param_index, ))) - if ntype.name in ['GLib.Array', 'GLib.PtrArray', 'GLib.ByteArray']: - attrs.append(('name', ntype.name)) - attrs.append(('c:type', ntype.ctype)) + attrs.insert(0, ('zero-terminated', '0')) if ntype.size is not None: - attrs.append(('fixed-size', ntype.size)) + attrs.append(('fixed-size', '%d' % (ntype.size, ))) + if ntype.length_param_name is not None: + assert function + attrs.insert(0, ('length', '%d' + % (function.get_parameter_index(ntype.length_param_name, )))) with self.tagcontext('array', attrs): - if ntype.element_type is not None: - self._write_type(ntype.element_type) - else: - self._write_type(Type(TYPE_ANY, ctype='gpointer')) - return - attrs = [('name', self._type_to_string(ntype))] - # FIXME: figure out if type references a basic type - # or a boxed/class/interface etc. and skip - # writing the ctype if the latter. - if type_cname is not None: - attrs.append(('c:type', type_cname)) - if (isinstance(ntype, List) - or typename in ('GLib.List', - 'GLib.SList')): + self._write_type(ntype.element_type) + elif isinstance(ntype, List): + if ntype.name: + attrs.insert(0, ('name', ntype.name)) with self.tagcontext('type', attrs): - if isinstance(ntype, List) and ntype.element_type: - self._write_type(ntype.element_type) - else: - self._write_type(Type(TYPE_ANY, ctype='gpointer')) - return - if isinstance(ntype, Map) and ntype.key_type: + self._write_type(ntype.element_type) + elif isinstance(ntype, Map): + attrs.insert(0, ('name', 'GLib.HashTable')) with self.tagcontext('type', attrs): self._write_type(ntype.key_type) self._write_type(ntype.value_type) - return - # Not a special type, just write it out - self.write_tag('type', attrs) + else: + # REWRITEFIXME - enable this for 1.2 + if ntype.target_giname: + attrs.insert(0, ('name', self._type_to_name(ntype))) + elif ntype.target_fundamental: + # attrs = [('fundamental', ntype.target_fundamental)] + attrs.insert(0, ('name', ntype.target_fundamental)) + elif ntype.target_foreign: + attrs.insert(0, ('foreign', '1')) + self.write_tag('type', attrs) def _write_enum(self, enum): attrs = [('name', enum.name)] @@ -325,20 +329,21 @@ and/or use gtk-doc annotations. ''') self.write_tag('member', attrs) def _write_constant(self, constant): - attrs = [('name', constant.name), - ('value', str(constant.value))] + attrs = [('name', constant.name), ('value', constant.value)] with self.tagcontext('constant', attrs): - self._write_type(constant.type) + self._write_type(constant.value_type) def _write_class(self, node): attrs = [('name', node.name), + ('c:symbol-prefix', node.c_symbol_prefix), ('c:type', node.ctype)] self._append_version(node, attrs) self._append_node_generic(node, attrs) if isinstance(node, Class): tag_name = 'class' if node.parent is not None: - attrs.append(('parent', node.parent)) + attrs.append(('parent', + self._type_to_name(node.parent))) if node.is_abstract: attrs.append(('abstract', '1')) else: @@ -348,7 +353,8 @@ and/or use gtk-doc annotations. ''') if node.get_type: attrs.append(('glib:get-type', node.get_type)) if node.glib_type_struct: - attrs.append(('glib:type-struct', node.glib_type_struct.name)) + attrs.append(('glib:type-struct', + self._type_to_name(node.glib_type_struct))) if isinstance(node, GLibObject): if node.fundamental: attrs.append(('glib:fundamental', '1')) @@ -364,10 +370,12 @@ and/or use gtk-doc annotations. ''') self._write_generic(node) if isinstance(node, GLibObject): for iface in sorted(node.interfaces): - self.write_tag('implements', [('name', iface)]) + self.write_tag('implements', + [('name', self._type_to_name(iface))]) if isinstance(node, Interface): for iface in sorted(node.prerequisites): - self.write_tag('prerequisite', [('name', iface)]) + self.write_tag('prerequisite', + [('name', self._type_to_name(iface))]) if isinstance(node, Class): for method in sorted(node.constructors): self._write_constructor(method) @@ -394,6 +402,8 @@ and/or use gtk-doc annotations. ''') self._write_constructor(method) for method in sorted(boxed.methods): self._write_method(method) + for method in sorted(boxed.static_methods): + self._write_static_method(method) def _write_property(self, prop): attrs = [('name', prop.name)] @@ -408,7 +418,8 @@ and/or use gtk-doc annotations. ''') attrs.append(('construct', '1')) if prop.construct_only: attrs.append(('construct-only', '1')) - attrs.append(('transfer-ownership', prop.transfer)) + if prop.transfer: + attrs.append(('transfer-ownership', prop.transfer)) with self.tagcontext('property', attrs): self._write_generic(prop) self._write_type(prop.type) @@ -416,16 +427,19 @@ and/or use gtk-doc annotations. ''') def _write_vfunc(self, vf): attrs = [] if vf.invoker: - attrs.append(('invoker', vf.invoker.name)) + attrs.append(('invoker', vf.invoker)) self._write_callable(vf, 'virtual-method', attrs) def _write_callback(self, callback): - attrs = [('c:type', callback.ctype)] + attrs = [] + if callback.namespace: + attrs.append(('c:type', callback.c_name)) self._write_callable(callback, 'callback', attrs) def _boxed_attrs(self, boxed): return [('glib:type-name', boxed.type_name), - ('glib:get-type', boxed.get_type)] + ('glib:get-type', boxed.get_type), + ('c:symbol-prefix', boxed.c_symbol_prefix)] def _write_record(self, record, extra_attrs=[]): is_gtype_struct = False @@ -442,7 +456,7 @@ and/or use gtk-doc annotations. ''') if record.is_gtype_struct_for: is_gtype_struct = True attrs.append(('glib:is-gtype-struct-for', - record.is_gtype_struct_for)) + self._type_to_name(record.is_gtype_struct_for))) self._append_version(record, attrs) self._append_node_generic(record, attrs) if isinstance(record, GLibBoxed): @@ -456,6 +470,8 @@ and/or use gtk-doc annotations. ''') self._write_constructor(method) for method in sorted(record.methods): self._write_method(method) + for method in sorted(record.static_methods): + self._write_static_method(method) def _write_union(self, union): attrs = [] @@ -476,27 +492,26 @@ and/or use gtk-doc annotations. ''') self._write_constructor(method) for method in sorted(union.methods): self._write_method(method) + for method in sorted(union.static_methods): + self._write_static_method(method) def _write_field(self, field, is_gtype_struct=False): - if isinstance(field, Function): - self._write_method(field) - return - - if isinstance(field, Callback): - attrs = [('name', field.name)] - with self.tagcontext('field', attrs): - self._write_generic(field) - if is_gtype_struct: - self._write_callback(field) - else: - attrs = [('name', TYPE_ANY), ('c:type', 'pointer')] - self.write_tag('type', attrs) - elif isinstance(field, Struct): - self._write_record(field) - elif isinstance(field, Union): - self._write_union(field) + if field.anonymous_node: + if isinstance(field.anonymous_node, Callback): + attrs = [('name', field.name)] + self._append_node_generic(field, attrs) + with self.tagcontext('field', attrs): + self._write_callback(field.anonymous_node) + elif isinstance(field.anonymous_node, Record): + self._write_record(field.anonymous_node) + elif isinstance(field.anonymous_node, Union): + self._write_union(field.anonymous_node) + else: + raise AssertionError("Unknown field anonymous: %r" \ + % (field.anonymous_node, )) else: attrs = [('name', field.name)] + self._append_node_generic(field, attrs) # Fields are assumed to be read-only # (see also girparser.c and generate.c) if not field.readable: @@ -516,4 +531,4 @@ and/or use gtk-doc annotations. ''') with self.tagcontext('glib:signal', attrs): self._write_generic(signal) self._write_return_type(signal.retval) - self._write_parameters(signal.parameters) + self._write_parameters(signal, signal.parameters) diff --git a/giscanner/glibast.py b/giscanner/glibast.py index ad87926f..85092b89 100644 --- a/giscanner/glibast.py +++ b/giscanner/glibast.py @@ -18,12 +18,11 @@ # Boston, MA 02111-1307, USA. # -from .ast import (Bitfield, Class, Enum, Interface, Member, Node, - Property, Union, Record) +from . import ast -class GLibRecord(Record): +class GLibRecord(ast.Record): def __init__(self, *args, **kwargs): - Record.__init__(self, *args, **kwargs) + ast.Record.__init__(self, *args, **kwargs) @classmethod def from_record(cls, record): @@ -38,10 +37,10 @@ class GLibRecord(Record): obj.is_gtype_struct_for = False return obj -class GLibEnum(Enum): +class GLibEnum(ast.Enum): def __init__(self, name, type_name, members, get_type): - Enum.__init__(self, name, type_name, members) + ast.Enum.__init__(self, name, type_name, members) self.ctype = type_name self.type_name = type_name self.get_type = get_type @@ -52,10 +51,10 @@ class GLibEnum(Enum): self.get_type) -class GLibFlags(Bitfield): +class GLibFlags(ast.Bitfield): def __init__(self, name, type_name, members, get_type): - Bitfield.__init__(self, name, type_name, members) + ast.Bitfield.__init__(self, name, type_name, members) self.ctype = type_name self.type_name = type_name self.get_type = get_type @@ -65,20 +64,21 @@ class GLibFlags(Bitfield): self.get_type) -class GLibEnumMember(Member): +class GLibEnumMember(ast.Member): def __init__(self, name, value, symbol, nick): - Member.__init__(self, name, value, symbol) + ast.Member.__init__(self, name, value, symbol) self.nick = nick -class GLibObject(Class): +class GLibObject(ast.Class): def __init__(self, name, parent, type_name, get_type, - is_abstract, ctype=None): - Class.__init__(self, name, parent, is_abstract) + c_symbol_prefix, is_abstract, ctype=None): + ast.Class.__init__(self, name, parent, is_abstract) self.type_name = type_name self.get_type = get_type + self.c_symbol_prefix = c_symbol_prefix self.fundamental = False self.unref_func = None self.ref_func = None @@ -87,60 +87,75 @@ class GLibObject(Class): self.signals = [] self.ctype = ctype or type_name + def _walk(self, callback, chain): + super(GLibObject, self)._walk(callback, chain) + for sig in self.signals: + sig.walk(callback, chain) + class GLibBoxed: - def __init__(self, type_name, get_type): + def __init__(self, type_name, get_type, c_symbol_prefix): self.type_name = type_name self.get_type = get_type + self.c_symbol_prefix = c_symbol_prefix +class GLibBoxedStruct(ast.Record, GLibBoxed): - -class GLibBoxedStruct(Record, GLibBoxed): - - def __init__(self, name, type_name, get_type, ctype=None): - Record.__init__(self, name, ctype or type_name) - GLibBoxed.__init__(self, type_name, get_type) + def __init__(self, name, type_name, get_type, c_symbol_prefix, ctype=None): + ast.Record.__init__(self, name, ctype or type_name) + GLibBoxed.__init__(self, type_name, get_type, c_symbol_prefix) -class GLibBoxedUnion(Union, GLibBoxed): +class GLibBoxedUnion(ast.Union, GLibBoxed): - def __init__(self, name, type_name, get_type, ctype=None): - Union.__init__(self, name, ctype or type_name) - GLibBoxed.__init__(self, type_name, get_type) + def __init__(self, name, type_name, get_type, c_symbol_prefix, ctype=None): + ast.Union.__init__(self, name, ctype or type_name) + GLibBoxed.__init__(self, type_name, get_type, c_symbol_prefix) -class GLibBoxedOther(Node, GLibBoxed): +class GLibBoxedOther(ast.Node, GLibBoxed): - def __init__(self, name, type_name, get_type): - Node.__init__(self, name) - GLibBoxed.__init__(self, type_name, get_type) + def __init__(self, name, type_name, get_type, c_symbol_prefix): + ast.Node.__init__(self, name) + GLibBoxed.__init__(self, type_name, get_type, c_symbol_prefix) self.constructors = [] self.methods = [] + self.static_methods = [] self.ctype = type_name self.doc = None + def _walk(self, callback, chain): + for ctor in self.constructors: + ctor.walk(callback, chain) + for meth in self.methods: + meth.walk(callback, chain) + for meth in self.static_methods: + meth.walk(callback, chain) -class GLibInterface(Interface): + +class GLibInterface(ast.Interface): def __init__(self, name, parent, type_name, get_type, - ctype=None): - Interface.__init__(self, name, parent) + c_symbol_prefix, ctype=None): + ast.Interface.__init__(self, name, parent) self.type_name = type_name self.get_type = get_type + self.c_symbol_prefix = c_symbol_prefix self.signals = [] self.ctype = ctype or type_name + def _walk(self, callback, chain): + super(GLibInterface, self)._walk(callback, chain) + for sig in self.signals: + sig.walk(callback, chain) -class GLibProperty(Property): +class GLibProperty(ast.Property): pass -class GLibSignal(Node): +class GLibSignal(ast.Callable): - def __init__(self, name, retval): - Node.__init__(self, name) - self.retval = retval - self.parameters = [] - self.doc = None + def __init__(self, name, retval, parameters): + ast.Callable.__init__(self, name, retval, parameters, False) diff --git a/giscanner/glibtransformer.py b/giscanner/glibtransformer.py deleted file mode 100644 index 560be915..00000000 --- a/giscanner/glibtransformer.py +++ /dev/null @@ -1,1205 +0,0 @@ -# -*- Mode: Python -*- -# GObject-Introspection - a framework for introspecting GObject libraries -# Copyright (C) 2008 Johan Dahlin -# -# This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 2 of the License, or (at your option) any later version. -# -# This library is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public -# License along with this library; if not, write to the -# Free Software Foundation, Inc., 59 Temple Place - Suite 330, -# Boston, MA 02111-1307, USA. -# - -import os -import sys -import re -import tempfile -import shutil -import subprocess - -from .ast import (Alias, Bitfield, Callable, Callback, Class, Constant, Enum, - Function, Interface, Member, Namespace, Node, Parameter, - Property, Record, Return, Type, TypeContainer, Union, - Field, VFunction, type_name_from_ctype, default_array_types, - TYPE_UINT8, PARAM_TRANSFER_FULL, Array, List, - TYPE_LONG_LONG, TYPE_LONG_DOUBLE, - Map, Varargs, type_names) -from .transformer import Names -from .glibast import (GLibBoxed, GLibEnum, GLibEnumMember, GLibFlags, - GLibInterface, GLibObject, GLibSignal, GLibBoxedStruct, - GLibBoxedUnion, GLibBoxedOther, GLibRecord) -from .utils import to_underscores, to_underscores_noprefix - -default_array_types['guchar*'] = TYPE_UINT8 - -# GParamFlags -G_PARAM_READABLE = 1 << 0 -G_PARAM_WRITABLE = 1 << 1 -G_PARAM_CONSTRUCT = 1 << 2 -G_PARAM_CONSTRUCT_ONLY = 1 << 3 -G_PARAM_LAX_VALIDATION = 1 << 4 -G_PARAM_STATIC_NAME = 1 << 5 -G_PARAM_STATIC_NICK = 1 << 6 -G_PARAM_STATIC_BLURB = 1 << 7 - -SYMBOL_BLACKLIST = [ - # These ones break GError conventions - 'g_simple_async_result_new_from_error', - 'g_simple_async_result_set_from_error', - 'g_simple_async_result_propagate_error', - 'g_simple_async_result_report_error_in_idle', - 'gtk_print_operation_get_error', -] - -SYMBOL_BLACKLIST_RE = [re.compile(x) for x in \ - [r'\w+_marshal_[A-Z]+__', ]] - -GET_TYPE_OVERRIDES = { - # this is a special case, from glibtransforer.py:create_gobject - 'intern': 'g_object_get_type', - # this is presumably a typo, should be fixed upstream - 'g_gstring_get_type': 'g_string_get_type', - # this is historical cruft: there's a deprecated - # #define gdk_window_get_type gdk_window_get_window_type - # upstream; this method can be renamed properly upstream once - # that deprecated alias is removed (in some future release) - 'gdk_window_object_get_type': 'gdk_window_get_type', -} - - -class IntrospectionBinary(object): - - def __init__(self, args, tmpdir=None): - self.args = args - if tmpdir is None: - self.tmpdir = tempfile.mkdtemp('', 'tmp-introspect') - else: - self.tmpdir = tmpdir - - -class Unresolved(object): - - def __init__(self, target): - self.target = target - - -class UnknownTypeError(Exception): - pass - - -class GLibTransformer(object): - - def __init__(self, transformer, noclosure=False): - self._transformer = transformer - self._noclosure = noclosure - self._namespace_name = None - self._names = Names() - self._uscore_type_names = {} - self._binary = None - self._get_type_functions = [] - self._error_quark_functions = [] - self._gtype_data = {} - self._failed_types = {} - self._boxed_types = {} - self._private_internal_types = {} - self._validating = False - - # Public API - - def init_parse(self): - """Do parsing steps that don't involve the introspection binary - - This does enough work that get_type_functions() can be called. - - """ - - namespace = self._transformer.parse() - self._namespace_name = namespace.name - self._namespace_version = namespace.version - - # First pass: parsing - for node in namespace.nodes: - self._parse_node(node) - - # We don't want an alias for this - it's handled specially in - # the typelib compiler. - if namespace.name == 'GObject': - del self._names.aliases['Type'] - - def get_get_type_functions(self): - return self._get_type_functions - - def set_introspection_binary(self, binary): - self._binary = binary - - def parse(self): - """Do remaining parsing steps requiring introspection binary""" - - # Get all the GObject data by passing our list of get_type - # functions to the compiled binary - - self._execute_binary() - - # Introspection is done from within parsing - - # Second pass: pair boxed structures - for boxed in self._boxed_types.itervalues(): - self._pair_boxed_type(boxed) - # Third pass: delete class structures, resolve - # all types we now know about - nodes = list(self._names.names.itervalues()) - for (ns, node) in nodes: - try: - self._resolve_node(node) - except KeyError, e: - self._transformer.log_node_warning(node, -"""Unresolvable entry %r""" % (e, )) - self._remove_attribute(node.name) - # Another pass, since we need to have the methods parsed - # in order to correctly modify them after class/record - # pairing - for (ns, node) in nodes: - # associate GtkButtonClass with GtkButton - if isinstance(node, Record): - self._pair_class_record(node) - for (ns, alias) in self._names.aliases.itervalues(): - self._resolve_alias(alias) - - self._resolve_quarks() - - # Our final pass replacing types - self._resolve_types(nodes) - - # Create a new namespace with what we found - namespace = Namespace(self._namespace_name, self._namespace_version) - namespace.nodes = map(lambda x: x[1], self._names.aliases.itervalues()) - for (ns, x) in self._names.names.itervalues(): - namespace.nodes.append(x) - return namespace - - # Private - - def _add_attribute(self, node, replace=False): - node_name = node.name - if (not replace) and node_name in self._names.names: - return - self._names.names[node_name] = (None, node) - - def _remove_attribute(self, name): - del self._names.names[name] - - def _get_attribute(self, name): - node = self._names.names.get(name) - if node: - return node[1] - return None - - def _lookup_node(self, name): - if name in type_names: - return None - node = self._get_attribute(name) - if node is None: - node = self._transformer.get_names().names.get(name) - if node: - return node[1] - return node - - def _get_no_uscore_prefixed_name(self, type_name): - # Besides the straight underscore conversion, we also try - # removing the underscores from the namespace as a possible C - # mapping; e.g. it's webkit_web_view, not web_kit_web_view - suffix = self._transformer.remove_prefix(type_name) - prefix = type_name[:-len(suffix)] - return (prefix + '_' + to_underscores(suffix)).lower() - - def _register_internal_type(self, type_name, node): - self._names.type_names[type_name] = (None, node) - uscored = to_underscores(type_name).lower() - # prefer the prefix of the get_type method, if there is one - if hasattr(node, 'get_type'): - uscored = GET_TYPE_OVERRIDES.get(node.get_type, node.get_type) - uscored = uscored[:-len('_get_type')] - self._uscore_type_names[uscored] = node - - no_uscore_prefixed = self._get_no_uscore_prefixed_name(type_name) - # since this is a guess, don't overwrite any 'real' prefix - if no_uscore_prefixed not in self._uscore_type_names: - self._uscore_type_names[no_uscore_prefixed] = node - - def _resolve_quarks(self): - # self._uscore_type_names is an authoritative mapping of types - # to underscored versions, since it is based on get_type() methods; - # but only covers enums that are registered as GObject enums. - # Create a fallback mapping based on all known enums in this module. - uscore_enums = {} - for enum in self._transformer.iter_enums(): - type_name = enum.symbol - uscored = to_underscores(type_name).lower() - - uscore_enums[uscored] = enum - - no_uscore_prefixed = self._get_no_uscore_prefixed_name(type_name) - if no_uscore_prefixed not in uscore_enums: - uscore_enums[no_uscore_prefixed] = enum - - for node in self._error_quark_functions: - short = node.symbol[:-len('_quark')] - if short == "g_io_error": - # Special case; GIOError was already taken forcing GIOErrorEnum - enum = self._names.type_names["GIOErrorEnum"][1] - else: - enum = self._uscore_type_names.get(short) - if enum is None: - enum = uscore_enums.get(short) - if enum is not None: - enum.error_quark = node.symbol - else: - self._transformer.log_node_warning(node, -"""Couldn't find corresponding enumeration""") - - # Helper functions - - def _resolve_gtypename(self, gtype_name): - try: - return self._transformer.gtypename_to_giname(gtype_name, - self._names) - except KeyError, e: - return Unresolved(gtype_name) - - def _resolve_gtypename_chain(self, gtype_names): - """Like _resolve_gtypename, but grab the first one that resolves. - If none of them do, return an Unresolved for the first.""" - for gtype_name in gtype_names: - try: - return self._transformer.gtypename_to_giname(gtype_name, - self._names) - except KeyError, e: - continue - return Unresolved(gtype_names[0]) - - def _execute_binary(self): - in_path = os.path.join(self._binary.tmpdir, 'types.txt') - f = open(in_path, 'w') - # TODO: Introspect GQuark functions - for func in self._get_type_functions: - f.write(func) - f.write('\n') - f.close() - out_path = os.path.join(self._binary.tmpdir, 'dump.xml') - - args = [] - args.extend(self._binary.args) - args.append('--introspect-dump=%s,%s' % (in_path, out_path)) - - # Invoke the binary, having written our get_type functions to types.txt - try: - subprocess.check_call(args, stdout=sys.stdout, stderr=sys.stderr) - except subprocess.CalledProcessError, e: - raise SystemExit(e) - self._read_introspect_dump(out_path) - - # Clean up temporaries - shutil.rmtree(self._binary.tmpdir) - - def _read_introspect_dump(self, xmlpath): - from xml.etree.cElementTree import parse - tree = parse(xmlpath) - root = tree.getroot() - for child in root: - self._gtype_data[child.attrib['name']] = child - for child in root: - self._introspect_type(child) - - def _create_gobject(self, node): - type_name = 'G' + node.name - if type_name == 'GObject': - parent_gitype = None - symbol = 'intern' - elif type_name == 'GInitiallyUnowned': - parent_type_name = 'GObject' - parent_gitype = self._resolve_gtypename(parent_type_name) - symbol = 'g_initially_unowned_get_type' - else: - assert False - gnode = GLibObject(node.name, parent_gitype, type_name, symbol, True) - if type_name == 'GObject': - gnode.fields.extend(node.fields) - else: - # http://bugzilla.gnome.org/show_bug.cgi?id=569408 - # GInitiallyUnowned is actually a typedef for GObject, but - # that's not reflected in the GIR, where it appears as a - # subclass (as it appears in the GType hierarchy). So - # what we do here is copy all of the GObject fields into - # GInitiallyUnowned so that struct offset computation - # works correctly. - gnode.fields = self._names.names['Object'][1].fields - self._add_attribute(gnode) - self._register_internal_type(type_name, gnode) - - # Parser - - def _parse_node(self, node): - if isinstance(node, Enum): - self._parse_enum(node) - elif isinstance(node, Bitfield): - self._parse_bitfield(node) - elif isinstance(node, Function): - self._parse_function(node) - elif isinstance(node, Record): - self._parse_record(node) - elif isinstance(node, Callback): - self._parse_callback(node) - elif isinstance(node, Alias): - self._parse_alias(node) - elif isinstance(node, Member): - # FIXME: atk_misc_instance singletons - pass - elif isinstance(node, Union): - self._parse_union(node) - elif isinstance(node, Constant): - self._parse_constant(node) - else: - print 'GLIB Transformer: Unhandled node:', node - - def _parse_alias(self, alias): - self._names.aliases[alias.name] = (None, alias) - - def _parse_enum(self, enum): - self._add_attribute(enum) - - def _parse_bitfield(self, enum): - self._add_attribute(enum) - - def _parse_constant(self, constant): - self._add_attribute(constant) - - def _parse_function(self, func): - if func.symbol in SYMBOL_BLACKLIST: - return - if func.symbol.startswith('_'): - return - for regexp in SYMBOL_BLACKLIST_RE: - if regexp.match(func.symbol): - return - if self._parse_get_type_function(func): - return - if self._parse_error_quark_function(func): - return - - self._add_attribute(func) - - def _parse_get_type_function(self, func): - symbol = func.symbol - if not symbol.endswith('_get_type'): - return False - if self._namespace_name == 'GLib': - # No GObjects in GLib - return False - if (self._namespace_name == 'GObject' and - symbol in ('g_object_get_type', 'g_initially_unowned_get_type')): - # We handle these internally, see _create_gobject - return True - if func.parameters: - return False - # GType *_get_type(void) - if func.retval.type.name not in ['Type', - 'GType', - 'GObject.Type', - 'Gtk.Type']: - self._transformer.log_("Warning: *_get_type function returns '%r'" - ", not GObject.Type") % (func.retval.type.name, ) - return False - - self._get_type_functions.append(symbol) - return True - - def _parse_error_quark_function(self, func): - if not func.symbol.endswith('_error_quark'): - return False - if func.parameters: - return False - if (func.retval.type.name != 'GLib.Quark' and - func.retval.type.ctype != 'GQuark'): - return False - - self._error_quark_functions.append(func) - return True - - def _name_is_internal_gtype(self, giname): - try: - node = self._get_attribute(giname) - return isinstance(node, (GLibObject, GLibInterface, - GLibBoxed, GLibEnum, GLibFlags)) - except KeyError, e: - return False - - def _parse_static_method(self, func): - components = func.symbol.split('_') - if len(components) < 2: - return None - target_klass = None - prefix_components = None - methname = None - for i in xrange(1, len(components)): - prefix_components = '_'.join(components[0:-i]) - methname = '_'.join(components[-i:]) - target_klass = self._uscore_type_names.get(prefix_components) - if target_klass and isinstance(target_klass, GLibObject): - break - target_klass = None - if not target_klass: - return None - self._remove_attribute(func.name) - func.name = methname - target_klass.static_methods.append(func) - func.is_method = True - return func - - def _parse_method(self, func): - if not func.parameters: - return False - return self._parse_method_common(func, True) - - def _parse_constructor(self, func): - return self._parse_method_common(func, False) - - def _parse_method_common(self, func, is_method): - # Skip _get_type functions, we processed them - # already - if func.symbol.endswith('_get_type'): - return None - - if not is_method: - target_arg = func.retval - else: - target_arg = func.parameters[0] - - if is_method: - # Methods require their first arg to be a known class - # Look at the original C type (before namespace stripping), without - # pointers: GtkButton -> gtk_button_, so we can figure out the - # method name - argtype = target_arg.type.ctype.replace('*', '') - name = self._transformer.remove_prefix(argtype) - name_uscore = to_underscores_noprefix(name).lower() - # prefer the prefix of the _get_type method, if there is one - if argtype in self._names.type_names: - node = self._names.type_names[argtype][1] - if hasattr(node, 'get_type'): - name_uscore = GET_TYPE_OVERRIDES.get(node.get_type, - node.get_type) - name_uscore = name_uscore[:-len('_get_type')] - name_offset = func.symbol.find(name_uscore + '_') - if name_offset < 0: - return None - prefix = func.symbol[:name_offset+len(name_uscore)] - else: - # Constructors must have _new - # Take everything before that as class name - new_idx = func.symbol.find('_new') - if new_idx < 0: - return None - # Constructors don't return basic types - derefed = self._transformer.follow_aliases(target_arg.type.name, - self._names) - if derefed in type_names: - #print "NOTE: Rejecting constructor returning basic: %r" \ - # % (func.symbol, ) - return None - prefix = func.symbol[:new_idx] - - klass = self._uscore_type_names.get(prefix) - if klass is None: - #print "NOTE: No valid matching class for likely "+\ - # "method or constructor: %r" % (func.symbol, ) - return None - # Enums can't have ctors or methods - if isinstance(klass, (GLibEnum, GLibFlags)): - return None - - # The _uscore_type_names member holds the plain GLibBoxed - # object; we want to actually use the struct/record associated - if isinstance(klass, (Record, Union)): - remove_prefix = klass.symbol - else: - remove_prefix = klass.type_name - - name = self._transformer.remove_prefix(remove_prefix) - klass = self._get_attribute(name) - if klass is None: - return - - if not is_method: - # Interfaces can't have constructors, punt to global scope - if isinstance(klass, GLibInterface): - #print "NOTE: Rejecting constructor for"+\ - # " interface type: %r" % (func.symbol, ) - return None - # TODO - check that the return type is a subclass of the - # class from the prefix - # But for now, ensure that constructor returns are always - # the most concrete class - name = self._transformer.remove_prefix(remove_prefix) - func.retval.type = Type(name, func.retval.type.ctype) - - self._remove_attribute(func.name) - # Strip namespace and object prefix: gtk_window_new -> new - func.name = func.symbol[len(prefix)+1:] - if is_method: - # We don't need the "this" parameter - del func.parameters[0] - klass.methods.append(func) - func.is_method = True - else: - klass.constructors.append(func) - return func - - def _parse_record(self, record): - # This is a hack, but GObject is a rather fundamental piece so. - internal_names = ["Object", 'InitiallyUnowned'] - g_internal_names = ["G" + x for x in internal_names] - if (self._namespace_name == 'GObject' and - record.name in internal_names): - self._create_gobject(record) - return - elif record.name in g_internal_names: - # Avoid duplicates - return - if record.name == 'InitiallyUnownedClass': - record.fields = self._names.names['ObjectClass'][1].fields - node = self._names.names.get(record.name) - if node is None: - self._add_attribute(record, replace=True) - self._register_internal_type(record.symbol, record) - return - (ns, node) = node - node.fields = record.fields[:] - - def _parse_union(self, union): - node = self._names.names.get(union.name) - if node is None: - self._add_attribute(union, replace=True) - self._register_internal_type(union.symbol, union) - return - (ns, node) = node - node.fields = union.fields[:] - - def _parse_callback(self, callback): - self._add_attribute(callback) - - def _strip_class_suffix(self, name): - if (name.endswith('Class') or - name.endswith('Iface')): - return name[:-5] - elif name.endswith('Interface'): - return name[:-9] - else: - return name - - def _arg_is_failed(self, param): - ctype = self._transformer.ctype_of(param).replace('*', '') - uscored = to_underscores(self._strip_class_suffix(ctype)).lower() - if uscored in self._failed_types: - print "Warning: failed type: %r" % (param, ) - return True - return False - - def _pair_class_record(self, maybe_class): - name = self._strip_class_suffix(maybe_class.name) - if name == maybe_class.name: - return - - class_struct = maybe_class - if self._arg_is_failed(class_struct): - print "WARNING: deleting no-type %r" % (class_struct.name, ) - del self._names.names[class_struct.name] - return - - pair_class = self._get_attribute(name) - if (not pair_class or - not isinstance(pair_class, (GLibObject, GLibInterface))): - return - - # Object class fields are assumed to be read-only - # (see also _introspect_object and transformer.py) - for field in maybe_class.fields: - if isinstance(field, Field): - field.writable = False - - # Loop through fields to determine which are virtual - # functions and which are signal slots by - # assuming everything that doesn't share a name - # with a known signal is a virtual slot. - for field in maybe_class.fields: - if not isinstance(field, Callback): - continue - # Check the first parameter is the object - if len(field.parameters) == 0: - continue - firstparam_type = field.parameters[0].type - if firstparam_type != pair_class: - continue - # Also double check we don't have a signal with this - # name. - matched_signal = False - for signal in pair_class.signals: - if signal.name.replace('-', '_') == field.name: - matched_signal = True - break - if matched_signal: - continue - vfunc = VFunction.from_callback(field) - vfunc.inherit_file_positions(field) - pair_class.virtual_methods.append(vfunc) - - # Take the set of virtual methods we found, and try - # to pair up with any matching methods using the - # name+signature. - for vfunc in pair_class.virtual_methods: - for method in pair_class.methods: - if (method.name != vfunc.name or - method.retval != vfunc.retval or - method.parameters != vfunc.parameters): - continue - vfunc.invoker = method - - gclass_struct = GLibRecord.from_record(class_struct) - self._remove_attribute(class_struct.name) - self._add_attribute(gclass_struct, True) - pair_class.glib_type_struct = gclass_struct - pair_class.inherit_file_positions(class_struct) - gclass_struct.is_gtype_struct_for = name - - # Introspection - - def _introspect_type(self, xmlnode): - if xmlnode.tag in ('enum', 'flags'): - self._introspect_enum(xmlnode) - elif xmlnode.tag == 'class': - self._introspect_object(xmlnode) - elif xmlnode.tag == 'interface': - self._introspect_interface(xmlnode) - elif xmlnode.tag == 'boxed': - self._introspect_boxed(xmlnode) - elif xmlnode.tag == 'fundamental': - self._introspect_fundamental(xmlnode) - else: - raise ValueError("Unhandled introspection XML tag %s", xmlnode.tag) - - def _introspect_enum(self, node): - members = [] - for member in node.findall('member'): - # Keep the name closer to what we'd take from C by default; - # see http://bugzilla.gnome.org/show_bug.cgi?id=575613 - name = member.attrib['nick'].replace('-', '_') - members.append(GLibEnumMember(name, - member.attrib['value'], - member.attrib['name'], - member.attrib['nick'])) - - klass = (GLibFlags if node.tag == 'flags' else GLibEnum) - type_name = node.attrib['name'] - enum_name = self._transformer.remove_prefix(type_name) - node = klass(enum_name, type_name, members, node.attrib['get-type']) - self._add_attribute(node, replace=True) - self._register_internal_type(type_name, node) - - def _introspect_object(self, xmlnode): - type_name = xmlnode.attrib['name'] - # We handle this specially above; in 2.16 and below there - # was no g_object_get_type, for later versions we need - # to skip it - if type_name == 'GObject': - return - # Get a list of parents here; some of them may be hidden, and what - # we really want to do is use the most-derived one that we know of. - # - parent_type_names = xmlnode.attrib['parents'].split(',') - parent_gitype = self._resolve_gtypename_chain(parent_type_names) - is_abstract = bool(xmlnode.attrib.get('abstract', False)) - node = GLibObject( - self._transformer.remove_prefix(type_name), - parent_gitype, - type_name, - xmlnode.attrib['get-type'], is_abstract) - self._introspect_properties(node, xmlnode) - self._introspect_signals(node, xmlnode) - self._introspect_implemented_interfaces(node, xmlnode) - - self._add_record_fields(node) - self._add_attribute(node, replace=True) - self._register_internal_type(type_name, node) - - def _introspect_interface(self, xmlnode): - type_name = xmlnode.attrib['name'] - node = GLibInterface( - self._transformer.remove_prefix(type_name), - None, - type_name, xmlnode.attrib['get-type']) - self._introspect_properties(node, xmlnode) - self._introspect_signals(node, xmlnode) - for child in xmlnode.findall('prerequisite'): - name = child.attrib['name'] - prereq = self._resolve_gtypename(name) - node.prerequisites.append(prereq) - # GtkFileChooserEmbed is an example of a private interface, we - # just filter them out - if xmlnode.attrib['get-type'].startswith('_'): - print "NOTICE: Marking %s as internal type" % (type_name, ) - self._private_internal_types[type_name] = node - else: - self._add_attribute(node, replace=True) - self._register_internal_type(type_name, node) - - def _introspect_boxed(self, xmlnode): - type_name = xmlnode.attrib['name'] - # This one doesn't go in the main namespace; we associate it with - # the struct or union - node = GLibBoxed(type_name, xmlnode.attrib['get-type']) - self._boxed_types[node.type_name] = node - self._register_internal_type(type_name, node) - - def _introspect_implemented_interfaces(self, node, xmlnode): - gt_interfaces = [] - for interface in xmlnode.findall('implements'): - gitype = self._resolve_gtypename(interface.attrib['name']) - gt_interfaces.append(gitype) - node.interfaces = gt_interfaces - - def _introspect_properties(self, node, xmlnode): - for pspec in xmlnode.findall('property'): - ctype = pspec.attrib['type'] - flags = int(pspec.attrib['flags']) - readable = (flags & G_PARAM_READABLE) != 0 - writable = (flags & G_PARAM_WRITABLE) != 0 - construct = (flags & G_PARAM_CONSTRUCT) != 0 - construct_only = (flags & G_PARAM_CONSTRUCT_ONLY) != 0 - node.properties.append(Property( - pspec.attrib['name'], - type_name_from_ctype(ctype), - readable, writable, construct, construct_only, - ctype, - )) - node.properties = node.properties - - def _introspect_signals(self, node, xmlnode): - for signal_info in xmlnode.findall('signal'): - rctype = signal_info.attrib['return'] - rtype = Type(self._transformer.parse_ctype(rctype), rctype) - return_ = Return(rtype, signal_info.attrib['return']) - return_.transfer = PARAM_TRANSFER_FULL - signal = GLibSignal(signal_info.attrib['name'], return_) - for i, parameter in enumerate(signal_info.findall('param')): - if i == 0: - name = 'object' - else: - name = 'p%s' % (i-1, ) - pctype = parameter.attrib['type'] - ptype = Type(self._transformer.parse_ctype(pctype), pctype) - param = Parameter(name, ptype) - param.transfer = 'none' - signal.parameters.append(param) - node.signals.append(signal) - node.signals = node.signals - - def _introspect_fundamental(self, xmlnode): - # We only care about types that can be instantiatable, other - # fundamental types such as the Clutter.Fixed/CoglFixed registers - # are not yet interesting from an introspection perspective and - # are ignored - if not xmlnode.attrib.get('instantiatable', False): - return - - type_name = xmlnode.attrib['name'] - - # Get a list of parents here; some of them may be hidden, and what - # we really want to do is use the most-derived one that we know of. - if 'parents' in xmlnode.attrib: - parent_type_names = xmlnode.attrib['parents'].split(',') - parent_gitype = self._resolve_gtypename_chain(parent_type_names) - else: - parent_gitype = None - is_abstract = bool(xmlnode.attrib.get('abstract', False)) - node = GLibObject( - self._transformer.remove_prefix(type_name), - parent_gitype, - type_name, - xmlnode.attrib['get-type'], is_abstract) - node.fundamental = True - self._introspect_implemented_interfaces(node, xmlnode) - - self._add_record_fields(node) - self._add_attribute(node, replace=True) - self._register_internal_type(type_name, node) - - def _add_record_fields(self, node): - # add record fields - record = self._get_attribute(node.name) - if record is None: - return - node.fields = record.fields - for field in node.fields: - if isinstance(field, Field): - # Object instance fields are assumed to be read-only - # (see also _pair_class_record and transformer.py) - field.writable = False - - def _pair_boxed_type(self, boxed): - name = self._transformer.remove_prefix(boxed.type_name) - pair_node = self._get_attribute(name) - if not pair_node: - boxed_item = GLibBoxedOther(name, boxed.type_name, - boxed.get_type) - elif isinstance(pair_node, Record): - boxed_item = GLibBoxedStruct(pair_node.name, boxed.type_name, - boxed.get_type) - boxed_item.inherit_file_positions(pair_node) - boxed_item.fields = pair_node.fields - elif isinstance(pair_node, Union): - boxed_item = GLibBoxedUnion(pair_node.name, boxed.type_name, - boxed.get_type) - boxed_item.inherit_file_positions(pair_node) - boxed_item.fields = pair_node.fields - else: - return False - self._add_attribute(boxed_item, replace=True) - - # Node walking - - def _walk(self, node, callback, chain): - if not isinstance(node, Node): - return - if not callback(node, chain): - return - chain.append(node) - def _subwalk(subnode): - self._walk(subnode, callback, chain) - if isinstance(node, (Callback, Callable)): - _subwalk(node.retval) - for parameter in node.parameters: - _subwalk(parameter) - elif isinstance(node, (Array, List)): - _subwalk(node.element_type) - elif isinstance(node, Map): - _subwalk(node.key_type) - _subwalk(node.value_type) - elif isinstance(node, Bitfield): - pass - elif isinstance(node, Record): - for ctor in node.constructors: - _subwalk(ctor) - for func in node.methods: - _subwalk(func) - elif isinstance(node, Field): - _subwalk(node.type) - elif isinstance(node, Class): - for meth in node.methods: - _subwalk(meth) - for meth in node.virtual_methods: - _subwalk(meth) - for meth in node.static_methods: - _subwalk(meth) - for ctor in node.constructors: - _subwalk(ctor) - for prop in node.properties: - _subwalk(prop) - for field in node.fields: - _subwalk(field) - elif isinstance(node, Interface): - for meth in node.methods: - _subwalk(meth) - for meth in node.virtual_methods: - _subwalk(meth) - for prop in node.properties: - _subwalk(prop) - for field in node.fields: - _subwalk(field) - elif isinstance(node, Constant): - _subwalk(node.type) - elif isinstance(node, Union): - for ctor in node.constructors: - _subwalk(ctor) - for meth in node.methods: - _subwalk(meth) - elif isinstance(node, GLibBoxed): - for ctor in node.constructors: - _subwalk(ctor) - for meth in node.methods: - _subwalk(meth) - - if isinstance(node, (GLibObject, GLibInterface)): - for sig in node.signals: - _subwalk(sig) - - chain.pop() - - # Resolver - - def _resolve_type_name(self, type_name, ctype=None): - # Workaround glib bug #548689, to be included in 2.18.0 - if type_name == "GParam": - type_name = "GObject.ParamSpec" - res = self._transformer.resolve_type_name_full - try: - return res(type_name, ctype, self._names) - except KeyError, e: - return self._transformer.resolve_type_name(type_name, ctype) - - def _resolve_param_type(self, ptype, **kwargs): - # Workaround glib bug #548689, to be included in 2.18.0 - if ptype.name == "GParam": - ptype.name = "GObject.ParamSpec" - elif ptype.name == "GObject.Strv": - return Array(None, ptype.ctype, Type('utf8')) - - return self._transformer.resolve_param_type_full(ptype, - self._names, - **kwargs) - - def _resolve_node(self, node): - if isinstance(node, Function): - self._resolve_function_toplevel(node) - - elif isinstance(node, Callback): - self._resolve_function(node) - elif isinstance(node, GLibObject): - self._resolve_glib_object(node) - elif isinstance(node, GLibInterface): - self._resolve_glib_interface(node) - elif isinstance(node, Record): - self._resolve_record(node) - elif isinstance(node, Union): - self._resolve_union(node) - elif isinstance(node, Alias): - self._resolve_alias(node) - - def _resolve_function_toplevel(self, func): - for parser in [self._parse_constructor, - self._parse_method, - self._parse_static_method]: - newfunc = parser(func) - if newfunc: - self._resolve_function(newfunc) - return - self._resolve_function(func) - - def _resolve_record(self, node): - for field in node.fields: - self._resolve_field(field) - - def _resolve_union(self, node): - for field in node.fields: - self._resolve_field(field) - - def _force_resolve(self, item, allow_unknown=False): - if isinstance(item, Unresolved): - if item.target in self._private_internal_types: - return None - try: - return self._transformer.gtypename_to_giname(item.target, - self._names) - except KeyError, e: - if allow_unknown: - self._transformer.log_warning( -"""Skipping unknown interface %s""" % (item.target, )) - return None - else: - raise - if item in self._private_internal_types: - return None - return item - - def _resolve_glib_interface(self, node): - node.parent = self._force_resolve(node.parent) - self._resolve_methods(node.methods) - self._resolve_properties(node.properties, node) - self._resolve_signals(node.signals) - node.prerequisites = filter(None, - [self._force_resolve(x, allow_unknown=True) - for x in node.prerequisites]) - - def _resolve_glib_object(self, node): - # If we can't find the parent class, just drop back to GObject. - # This supports hidden parent classes. - # http://bugzilla.gnome.org/show_bug.cgi?id=561360 - try: - node.parent = self._force_resolve(node.parent) - except KeyError, e: - #print ("WARNING: Parent %r of class %r" +\ - # " not found; using GObject") % (node.parent.target, - # node.name) - node.parent = self._transformer.gtypename_to_giname("GObject", - self._names) - node.interfaces = filter(None, - [self._force_resolve(x, allow_unknown=True) - for x in node.interfaces]) - self._resolve_constructors(node.constructors) - self._resolve_methods(node.methods) - self._resolve_methods(node.static_methods) - self._resolve_properties(node.properties, node) - self._resolve_signals(node.signals) - for field in node.fields: - self._resolve_field(field) - - def _resolve_glib_boxed(self, node): - self._resolve_constructors(node.constructors) - self._resolve_methods(node.methods) - - def _resolve_constructors(self, constructors): - for ctor in constructors: - self._resolve_function(ctor) - - def _resolve_methods(self, methods): - for method in methods: - self._resolve_function(method) - - def _resolve_signals(self, signals): - for signal in signals: - self._resolve_function(signal) - - def _resolve_properties(self, properties, context): - failed = [] - for prop in properties: - try: - self._resolve_property(prop) - except KeyError, e: - failed.append(prop) - for fail in failed: - #print ("WARNING: Deleting object property %r (of %r) " - # "with unknown type") % (fail, context) - properties.remove(fail) - - def _resolve_property(self, prop): - prop.type = self._resolve_param_type(prop.type, allow_invalid=False) - - def _adjust_throws(self, func): - if func.parameters == []: - return - - last_param = func.parameters.pop() - - # Checking type.name=='GLib.Error' generates false positives - # on methods that take a 'GError *' - if last_param.type.ctype == 'GError**': - func.throws = True - else: - func.parameters.append(last_param) - - def _resolve_function(self, func): - self._resolve_parameters(func.parameters) - func.retval.type = self._resolve_param_type(func.retval.type) - self._adjust_throws(func) - - def _resolve_parameters(self, parameters): - for parameter in parameters: - parameter.type = self._resolve_param_type(parameter.type) - - def _resolve_field(self, field): - if isinstance(field, Callback): - self._resolve_function(field) - elif isinstance(field, Record): # non-typedef'd struct - self._resolve_record(field) - elif isinstance(field, Union): # non-typedef'd union - self._resolve_union(field) - else: - field.type = self._resolve_param_type(field.type) - - def _resolve_alias(self, alias): - alias.target = self._resolve_type_name(alias.target, alias.target) - - def _resolve_types(self, nodes): - nodes = list(self._names.names.itervalues()) - i = 0 - self._validating = True - while True: - initlen = len(nodes) - - nodes = list(self._names.names.itervalues()) - for node in nodes: - try: - self._resolve_node(node) - except UnknownTypeError, e: - print "WARNING: %s: Deleting %r" % (e, node) - self._remove_attribute(node.name) - if len(nodes) == initlen: - break - i += 1 - self._validating = False - - # Validation - - def _interface_vfunc_check(self, node, stack): - if isinstance(node, GLibInterface): - for vfunc in node.virtual_methods: - if not vfunc.invoker: - self._transformer.log_node_warning(vfunc, -"""Virtual function %r has no known invoker""" % (vfunc.name, ), - context=node) - - def _is_unannotated_list(self, node): - # Already annotated - if isinstance(node.type, List): - return False - if (node.type.name == 'GLib.List' or - node.type.name == 'GLib.SList'): - return True - if (self._transformer._namespace.name == 'GLib' and - (node.type.name == 'List' or - node.type.name == 'SList')): - return True - return False - - def _introspectable_analysis(self, node, stack): - if isinstance(node, TypeContainer): - parent = stack[-1] - if node.type.name in [TYPE_LONG_LONG, TYPE_LONG_DOUBLE]: - parent.introspectable = False - elif isinstance(node.type, Varargs): - parent.introspectable = False - elif self._is_unannotated_list(node): - if isinstance(node, Parameter): - self._transformer.log_node_warning(parent, -"""Missing (element-type) annotation on argument %r""" % (node.name, ), - context=parent) - else: - self._transformer.log_node_warning(parent, -"""Missing (element-type) annotation on return value""", context=parent) - parent.introspectable = False - - def _analyze_node(self, node, stack): - if node.skip: - return False - # Combine one-pass checks here - self._interface_vfunc_check(node, stack) - # Our first pass for scriptability - self._introspectable_analysis(node, stack) - return True - - def _introspectable_pass2(self, node, stack): - if node.skip: - return False - # In the second introspectable pass, we propagate introspectablity; - # for example, a varargs callback as an argument to a function - # makes the whole function unintrospectable - if isinstance(node, TypeContainer): - parent = stack[-1] - target = self._lookup_node(node.type.name) - if target and not target.introspectable: - parent.introspectable = False - return True - - # This function is called at the very end, before we hand back the - # completed namespace to the writer. Add static analysis checks here. - def final_analyze(self): - for (ns, node) in self._names.names.itervalues(): - self._walk(node, self._analyze_node, []) - for (ns, node) in self._names.names.itervalues(): - self._walk(node, self._introspectable_pass2, []) diff --git a/giscanner/introspectablepass.py b/giscanner/introspectablepass.py new file mode 100644 index 00000000..a8809120 --- /dev/null +++ b/giscanner/introspectablepass.py @@ -0,0 +1,159 @@ +# -*- Mode: Python -*- +# Copyright (C) 2010 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from . import ast +from . import glibast + +class IntrospectablePass(object): + + def __init__(self, transformer): + self._transformer = transformer + self._namespace = transformer.namespace + + # Public API + + def validate(self): + self._namespace.walk(self._analyze_node) + self._namespace.walk(self._introspectable_callable_analysis) + self._namespace.walk(self._introspectable_callable_analysis) + self._namespace.walk(self._introspectable_pass3) + + def _interface_vfunc_check(self, node, stack): + if isinstance(node, glibast.GLibInterface): + for vfunc in node.virtual_methods: + if not vfunc.invoker: + self._transformer.log_node_warning(vfunc, +"""Virtual function %r has no known invoker""" % (vfunc.name, ), + context=node) + + def _parameter_warning(self, parent, param, text, *args): + if hasattr(parent, 'symbol'): + prefix = '%s: ' % (parent.symbol, ) + else: + prefix = '' + if isinstance(param, ast.Parameter): + context = "argument %s: " % (param.argname, ) + else: + context = "return value: " + self._transformer.log_node_warning(parent, prefix + context + text, *args) + + def _introspectable_param_analysis(self, parent, node): + if not node.type.resolved: + self._parameter_warning(parent, node, "Unresolved ctype: %r" % (node.type.ctype, )) + parent.introspectable = False + elif isinstance(node.type, ast.Varargs): + parent.introspectable = False + elif not isinstance(node.type, ast.List) and \ + (node.type.target_giname == 'GLib.List'): + self._parameter_warning(parent, node, "Missing (element-type) annotation") + parent.introspectable = False + elif node.transfer is None: + self._parameter_warning(parent, node, "Missing (transfer) annotation") + parent.introspectable = False + + if isinstance(node, ast.Parameter) and node.type.target_giname: + target = self._transformer.lookup_typenode(node.type) + if (isinstance(target, ast.Callback) + and not target.create_type().target_giname in ('GLib.DestroyNotify', + 'Gio.AsyncReadyCallback') + and node.scope is None): + self._parameter_warning(parent, node, + ("Missing (scope) annotation for callback" + + " without GDestroyNotify (valid: %s, %s)") + % (ast.PARAM_SCOPE_CALL, ast.PARAM_SCOPE_ASYNC)) + parent.introspectable = False + + def _type_is_introspectable(self, typeval, warn=False): + if not typeval.resolved: + return False + if isinstance(typeval, (ast.Array, ast.List)): + return self._type_is_introspectable(typeval.element_type) + elif isinstance(typeval, ast.Map): + return (self._type_is_introspectable(typeval.key_type) + and self._type_is_introspectable(typeval.value_type)) + if typeval.target_foreign: + return True + if typeval.target_fundamental: + if typeval.is_equiv(ast.TYPE_VALIST): + return False + # Mark UCHAR as not introspectable temporarily until + # we're ready to land the typelib changes + if typeval.is_equiv(ast.TYPE_UNICHAR): + return False + # These are not introspectable pending us adding + # larger type tags to the typelib (in theory these could + # be 128 bit or larger) + if typeval.is_equiv((ast.TYPE_LONG_LONG, ast.TYPE_LONG_ULONG, + ast.TYPE_LONG_DOUBLE)): + return False + return True + target = self._transformer.lookup_typenode(typeval) + if not target: + return False + return target.introspectable + + def _analyze_node(self, obj, stack): + if obj.skip: + return False + # Combine one-pass checks here + self._interface_vfunc_check(obj, stack) + # Our first pass for scriptability + if isinstance(obj, ast.Callable): + for param in obj.parameters: + self._introspectable_param_analysis(obj, param) + self._introspectable_param_analysis(obj, obj.retval) + if isinstance(obj, (ast.Class, ast.Interface, ast.Record, ast.Union)): + for field in obj.fields: + if field.type: + if not self._type_is_introspectable(field.type): + field.introspectable = False + return True + + def _introspectable_callable_analysis(self, obj, stack): + if obj.skip: + return True + # Propagate introspectability of parameters to entire functions + if isinstance(obj, ast.Callable): + for param in obj.parameters: + if not self._type_is_introspectable(param.type): + obj.introspectable = False + return True + if not self._type_is_introspectable(obj.retval.type): + obj.introspectable = False + return True + return True + + def _introspectable_pass3(self, obj, stack): + if obj.skip: + return True + # Propagate introspectability for fields + if isinstance(obj, (ast.Class, ast.Interface, ast.Record, ast.Union)): + for field in obj.fields: + if field.anonymous_node: + if not field.anonymous_node.introspectable: + field.introspectable = False + else: + if not self._type_is_introspectable(field.type): + field.introspectable = False + # Propagate introspectability for properties + if isinstance(obj, (ast.Class, ast.Interface)): + for prop in obj.properties: + if not self._type_is_introspectable(prop.type): + prop.introspectable = False + return True diff --git a/giscanner/maintransformer.py b/giscanner/maintransformer.py new file mode 100644 index 00000000..ac180155 --- /dev/null +++ b/giscanner/maintransformer.py @@ -0,0 +1,933 @@ +# -*- Mode: Python -*- +# Copyright (C) 2010 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +import re + +from . import ast +from . import glibast +from .annotationparser import (TAG_VFUNC, TAG_SINCE, TAG_DEPRECATED, TAG_RETURNS, + TAG_ATTRIBUTES, TAG_RENAME_TO, TAG_TYPE, TAG_TRANSFER, + TAG_UNREF_FUNC, TAG_REF_FUNC, TAG_SET_VALUE_FUNC, + TAG_GET_VALUE_FUNC) +from .annotationparser import (OPT_ALLOW_NONE, + OPT_ARRAY, OPT_ELEMENT_TYPE, OPT_IN, OPT_INOUT, + OPT_INOUT_ALT, OPT_OUT, OPT_SCOPE, + OPT_TYPE, OPT_CLOSURE, OPT_DESTROY, OPT_SKIP, + OPT_FOREIGN, OPT_ARRAY_FIXED_SIZE, + OPT_ARRAY_LENGTH, OPT_ARRAY_ZERO_TERMINATED) +from .annotationparser import AnnotationParser +from .utils import to_underscores, to_underscores_noprefix + +class MainTransformer(object): + + def __init__(self, transformer, blocks): + self._transformer = transformer + self._blocks = blocks + self._namespace = transformer.namespace + self._uscore_type_names = {} + + # Public API + + def transform(self): + # We have a rough tree which should have most of of the types + # we know about. Let's attempt closure; walk over all of the + # Type() types and see if they match up with something. + self._namespace.walk(self._pass_type_resolution) + + # Determine some default values for transfer etc. + # based on the current tree. + self._namespace.walk(self._pass_callable_defaults) + + # Read in most annotations now. + self._namespace.walk(self._pass_read_annotations) + + # Now that we've possibly seen more types from annotations, + # do another type resolution pass. + self._namespace.walk(self._pass_type_resolution) + + # Generate a reverse mapping "bar_baz" -> BarBaz + for node in self._namespace.itervalues(): + if isinstance(node, (ast.Class, ast.Interface, glibast.GLibBoxed)): + self._uscore_type_names[node.c_symbol_prefix] = node + elif isinstance(node, (ast.Record, ast.Union)): + uscored = to_underscores_noprefix(node.name).lower() + self._uscore_type_names[uscored] = node + + for node in list(self._namespace.itervalues()): + if isinstance(node, ast.Function): + # Discover which toplevel functions are actually methods + self._pair_function(node) + if isinstance(node, (ast.Class, ast.Interface)): + self._pair_class_virtuals(node) + + # Some annotations need to be post function pairing + self._namespace.walk(self._pass_read_annotations2) + + # Another type resolution pass after we've parsed virtuals, etc. + self._namespace.walk(self._pass_type_resolution) + + self._namespace.walk(self._pass3) + + # TODO - merge into pass3 + self._resolve_quarks() + + # Private + + def _get_validate_parameter_name(self, parent, param_name, origin): + try: + param = parent.get_parameter(param_name) + except ValueError, e: + param = None + if param is None: + if isinstance(origin, ast.Parameter): + origin_name = 'parameter %s' % (origin.argname, ) + else: + origin_name = 'return value' + self._transformer.log_node_warning(parent, + "can't find parameter %s referenced by %s of %r" + % (param_name, origin_name, parent.name), fatal=True) + + return param.argname + + def _apply_annotation_rename_to(self, node, chain, block): + if not block: + return + rename_to = block.get(TAG_RENAME_TO) + if not rename_to: + return + rename_to = rename_to.value + target = self._namespace.get_by_symbol(rename_to) + if not target: + self._transformer.log_node_warning(node, +"Can't find symbol %r referenced by Rename annotation" % (rename_to, )) + elif target.shadowed_by: + self._transformer.log_node_warning(node, +"Function %r already shadowed by %r, can't overwrite with %r" % (target.symbol, + target.shadowed_by, + rename_to)) + elif target.shadows: + self._transformer.log_node_warning(node, +"Function %r already shadows %r, can't multiply shadow with %r" % (target.symbol, + target.shadows, + rename_to)) + else: + target.shadows = node.symbol + node.shadowed_by = target.symbol + + def _apply_annotations_function(self, node, chain): + block = self._blocks.get(node.symbol) + self._apply_annotations_callable(node, chain, block) + self._apply_annotation_rename_to(node, chain, block) + + def _pass_callable_defaults(self, node, chain): + if isinstance(node, (ast.Callable, glibast.GLibSignal)): + for param in node.parameters: + if param.transfer is None: + param.transfer = self._get_transfer_default(node, param) + if node.retval.transfer is None: + node.retval.transfer = self._get_transfer_default(node, node.retval) + return True + + def _pass_read_annotations(self, node, chain): + if not node.namespace: + return False + if isinstance(node, ast.Function): + self._apply_annotations_function(node, chain) + if isinstance(node, ast.Callback): + block = self._blocks.get(node.c_name) + self._apply_annotations_callable(node, chain, block) + if isinstance(node, (ast.Class, ast.Interface, ast.Record, + ast.Union, ast.Enum, ast.Bitfield, + ast.Callback)): + block = self._blocks.get(node.c_name) + self._apply_annotations_annotated(node, block) + if isinstance(node, (ast.Class, ast.Interface, ast.Record, ast.Union)): + for field in node.fields: + self._blocks.get('%s::%s' % (node.c_name, field.name)) + self._apply_annotations_field(node, block, field) + if isinstance(node, (ast.Class, ast.Interface)): + for prop in node.properties: + self._apply_annotations_property(node, prop) + for sig in node.signals: + self._apply_annotations_signal(node, sig) + if isinstance(node, ast.Class): + block = self._blocks.get(node.c_name) + if block: + tag = block.get(TAG_UNREF_FUNC) + node.unref_func = tag.value if tag else None + tag = block.get(TAG_REF_FUNC) + node.ref_func = tag.value if tag else None + tag = block.get(TAG_SET_VALUE_FUNC) + node.set_value_func = tag.value if tag else None + tag = block.get(TAG_GET_VALUE_FUNC) + node.get_value_func = tag.value if tag else None + return True + + def _adjust_container_type(self, parent, node, options): + has_element_type = OPT_ELEMENT_TYPE in options + has_array = OPT_ARRAY in options + + if has_array: + self._apply_annotations_array(parent, node, options) + elif has_element_type: + self._apply_annotations_element_type(parent, node, options) + + 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): + res = self._transformer.create_type_from_user_string(ident) + return res + def combiner(base, *rest): + if not rest: + return base + if isinstance(base, ast.List) and len(rest) == 1: + return ast.List(base.name, *rest) + if isinstance(base, ast.Map) and len(rest) == 2: + return ast.Map(*rest) + self._transformer.log_warning( +"Too many parameters in type specification %r" % (type_str, )) + return base + def top_combiner(base, *rest): + if orig_node is not None: + base.is_const = orig_node.is_const + return combiner(base, *rest) + + result, rest = grab_one(type_str, resolver, top_combiner, combiner) + if rest: + self._transformer.log_warning( +"Trailing components in type specification %r" % (type_str, )) + return result + + def _apply_annotations_array(self, parent, node, options): + array_opt = options.get(OPT_ARRAY) + if array_opt: + array_values = array_opt.all() + else: + array_values = {} + + element_type = options.get(OPT_ELEMENT_TYPE) + if element_type is not None: + element_type_node = self._resolve(element_type.one()) + elif isinstance(node.type, ast.Array): + element_type_node = node.type.element_type + else: + # We're assuming here that Foo* with an (array) annotation + # and no (element-type) means array of Foo + element_type_node = node.type.clone() + # Explicitly erase ctype since it's no longer valid + element_type_node.ctype = None + + if isinstance(node.type, ast.Array): + array_type = node.type.array_type + else: + array_type = None + container_type = ast.Array(array_type, element_type_node, + ctype=node.type.ctype, + is_const=node.type.is_const) + if OPT_ARRAY_ZERO_TERMINATED in array_values: + container_type.zeroterminated = array_values.get( + OPT_ARRAY_ZERO_TERMINATED) == '1' + length = array_values.get(OPT_ARRAY_LENGTH) + if length is not None: + paramname = self._get_validate_parameter_name(parent, length, node) + if paramname: + param = parent.get_parameter(paramname) + param.direction = node.direction + if param.direction == ast.PARAM_DIRECTION_OUT: + param.transfer = ast.PARAM_TRANSFER_FULL + container_type.length_param_name = param.argname + fixed = array_values.get(OPT_ARRAY_FIXED_SIZE) + if fixed: + container_type.size = int(fixed) + node.type = container_type + + def _apply_annotations_element_type(self, parent, node, options): + element_type_opt = options.get(OPT_ELEMENT_TYPE) + element_type = element_type_opt.flat() + if isinstance(node.type, ast.List): + assert len(element_type) == 1 + node.type.element_type = self._resolve(element_type[0]) + elif isinstance(node.type, ast.Map): + assert len(element_type) == 2 + node.type.key_type = self._resolve(element_type[0]) + node.type.value_type = self._resolve(element_type[1]) + elif isinstance(node.type, ast.Array): + node.type.element_type = self._resolve(element_type[0]) + else: + self._transformer.log_node_warning(parent, + "Unknown container %r for element-type annotation" % (node.type, )) + + def _get_transfer_default_param(self, parent, node): + if node.direction in [ast.PARAM_DIRECTION_INOUT, + ast.PARAM_DIRECTION_OUT]: + if node.caller_allocates: + return ast.PARAM_TRANSFER_NONE + return ast.PARAM_TRANSFER_FULL + return ast.PARAM_TRANSFER_NONE + + def _get_transfer_default_returntype_basic(self, typeval): + if (typeval.is_equiv(ast.BASIC_GIR_TYPES) + or typeval.is_const + or typeval.is_equiv(ast.TYPE_NONE)): + return ast.PARAM_TRANSFER_NONE + elif typeval.is_equiv(ast.TYPE_STRING): + # Non-const strings default to FULL + return ast.PARAM_TRANSFER_FULL + elif typeval.target_fundamental: + # This looks like just GType right now + return None + return None + + def _is_gi_subclass(self, typeval, supercls_type): + cls = self._transformer.lookup_typenode(typeval) + assert cls, str(typeval) + supercls = self._transformer.lookup_typenode(supercls_type) + assert supercls + if cls is supercls: + return True + if cls.parent: + return self._is_gi_subclass(cls.parent, supercls_type) + return False + + def _get_transfer_default_return(self, parent, node): + typeval = node.type + basic = self._get_transfer_default_returntype_basic(typeval) + if basic: + return basic + if not typeval.target_giname: + return None + target = self._transformer.lookup_typenode(typeval) + if isinstance(target, ast.Alias): + return self._get_transfer_default_returntype_basic(target.target) + elif isinstance(target, glibast.GLibBoxed): + return ast.PARAM_TRANSFER_FULL + elif isinstance(target, (ast.Enum, ast.Bitfield)): + return ast.PARAM_TRANSFER_NONE + # Handle constructors specially here + elif isinstance(parent, ast.Function) and parent.is_constructor: + if isinstance(target, ast.Class): + initially_unowned_type = ast.Type(target_giname='GObject.InitiallyUnowned') + initially_unowned = self._transformer.lookup_typenode(initially_unowned_type) + if initially_unowned and self._is_gi_subclass(typeval, initially_unowned_type): + return ast.PARAM_TRANSFER_NONE + else: + return ast.PARAM_TRANSFER_FULL + elif isinstance(target, (ast.Record, ast.Union)): + return ast.PARAM_TRANSFER_FULL + else: + assert False, "Invalid constructor" + elif isinstance(target, (ast.Class, ast.Record, ast.Union)): + # Explicitly no default for these + return None + else: + return None + + def _get_transfer_default(self, parent, node): + if node.type.is_equiv(ast.TYPE_NONE) or isinstance(node.type, ast.Varargs): + return ast.PARAM_TRANSFER_NONE + elif isinstance(node, ast.Parameter): + return self._get_transfer_default_param(parent, node) + elif isinstance(node, ast.Return): + return self._get_transfer_default_return(parent, node) + elif isinstance(node, ast.Field): + return ast.PARAM_TRANSFER_NONE + elif isinstance(node, ast.Property): + return ast.PARAM_TRANSFER_NONE + else: + raise AssertionError(node) + + def _apply_annotations_param_ret_common(self, parent, node, tag): + options = getattr(tag, 'options', {}) + + param_type = options.get(OPT_TYPE) + if param_type: + node.type = self._resolve(param_type.one(), node.type) + + caller_allocates = False + annotated_direction = None + if (OPT_INOUT in options or + OPT_INOUT_ALT in options): + annotated_direction = ast.PARAM_DIRECTION_INOUT + elif OPT_OUT in options: + subtype = options[OPT_OUT] + if subtype is not None: + subtype = subtype.one() + annotated_direction = ast.PARAM_DIRECTION_OUT + if subtype in (None, ''): + if node.type.target_giname and node.type.ctype: + caller_allocates = '**' not in node.type.ctype + else: + caller_allocates = False + elif subtype == 'caller-allocates': + caller_allocates = True + elif subtype == 'callee-allocates': + caller_allocates = False + else: + self._transformer.log_warning( +"out allocation for %s is invalid (%r)" % (node, subtype), fatal=True) + elif OPT_IN in options: + annotated_direction = ast.PARAM_DIRECTION_IN + + if (annotated_direction is not None) and (annotated_direction != node.direction): + node.direction = annotated_direction + node.caller_allocates = caller_allocates + # Also reset the transfer default if we're toggling direction + node.transfer = self._get_transfer_default(parent, node) + + transfer_tag = options.get(TAG_TRANSFER) + if transfer_tag: + node.transfer = transfer_tag.one() + + self._adjust_container_type(parent, node, options) + + if (OPT_ALLOW_NONE in options or + node.type.target_giname == 'Gio.Cancellable'): + node.allow_none = True + + if tag is not None and tag.comment is not None: + node.doc = tag.comment + + for key in options: + if '.' in key: + value = options.get(key) + if value: + node.attributes.append((key, value.one())) + + def _apply_annotations_annotated(self, node, block): + if block is None: + return + + node.doc = block.comment + + since_tag = block.get(TAG_SINCE) + if since_tag is not None: + node.version = since_tag.value + + deprecated_tag = block.get(TAG_DEPRECATED) + if deprecated_tag is not None: + value = deprecated_tag.value + if ': ' in value: + version, desc = value.split(': ') + else: + desc = value + version = None + node.deprecated = desc + if version is not None: + node.deprecated_version = version + + annos_tag = block.get(TAG_ATTRIBUTES) + if annos_tag is not None: + options = AnnotationParser.parse_options(annos_tag.value) + for key, value in options.iteritems(): + if value: + node.attributes.append((key, value.one())) + + if OPT_SKIP in block.options: + node.skip = True + + if OPT_FOREIGN in block.options: + node.foreign = True + + def _apply_annotations_param(self, parent, param, tag): + if tag: + options = tag.options + else: + options = {} + if isinstance(parent, ast.Function): + scope = options.get(OPT_SCOPE) + if scope: + scope = scope.one() + if scope not in [ast.PARAM_SCOPE_CALL, + ast.PARAM_SCOPE_ASYNC, + ast.PARAM_SCOPE_NOTIFIED]: + self._transformer.log_warning(parent, +"Invalid scope %r for parameter %r" % (scope, param.name)) + else: + param.scope = scope + param.transfer = ast.PARAM_TRANSFER_NONE + + destroy = options.get(OPT_DESTROY) + if destroy: + param.destroy_name = self._get_validate_parameter_name(parent, + destroy.one(), + param) + if param.destroy_name is not None: + param.scope = ast.PARAM_SCOPE_NOTIFIED + destroy_param = parent.get_parameter(param.destroy_name) + # This is technically bogus; we're setting the scope on the destroy + # itself. But this helps avoid tripping a warning from finaltransformer, + # since we don't have a way right now to flag this callback a destroy. + destroy_param.scope = ast.PARAM_SCOPE_NOTIFIED + closure = options.get(OPT_CLOSURE) + if closure: + param.closure_name = self._get_validate_parameter_name(parent, + closure.one(), + param) + elif isinstance(parent, ast.Callback): + if OPT_CLOSURE in options: + # For callbacks, (closure) appears without an + # argument, and tags a parameter that is a closure. We + # represent it (weirdly) in the gir and typelib by + # setting param.closure_name to itself. + param.closure_name = param.argname + + self._apply_annotations_param_ret_common(parent, param, tag) + + def _apply_annotations_return(self, parent, return_, block): + if block: + tag = block.get(TAG_RETURNS) + else: + tag = None + self._apply_annotations_param_ret_common(parent, return_, tag) + + def _apply_annotations_params(self, parent, params, block): + for param in params: + if block: + tag = block.get(param.argname) + else: + tag = None + self._apply_annotations_param(parent, param, tag) + + def _apply_annotations_callable(self, node, chain, block): + self._apply_annotations_annotated(node, block) + self._apply_annotations_params(node, node.parameters, block) + self._apply_annotations_return(node, node.retval, block) + + def _check_arg_annotations(self, parent, params, block): + if block is None: + return + for tag in block.tags.keys(): + if tag == TAG_RETURNS: + continue + for param in params: + if param.argname == tag: + break + else: + self._transformer.log_warning( +"Annotation for '%s' refers to unknown argument '%s'" +% (parent.name, tag)) + + def _apply_annotations_field(self, parent, block, field): + if not block: + return + tag = block.get(field.name) + if not tag: + return + t = tag.options.get('type') + if not t: + return + field.type = self._transformer.create_type_from_user_string(t.one()) + + def _apply_annotations_property(self, parent, prop): + block = self._blocks.get('%s:%s' % (parent.type_name, prop.name)) + self._apply_annotations_annotated(prop, block) + if not block: + return + transfer_tag = block.get(TAG_TRANSFER) + if transfer_tag is not None: + prop.transfer = transfer_tag.value + else: + prop.transfer = self._get_transfer_default(parent, prop) + type_tag = block.get(TAG_TYPE) + if type_tag: + prop.type = self._resolve(type_tag.value, prop.type) + + def _apply_annotations_signal(self, parent, signal): + block = self._blocks.get('%s::%s' % (parent.type_name, signal.name)) + self._apply_annotations_annotated(signal, 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 + if block and len(block.tags) > len(signal.parameters): + names = block.tags.items() + else: + names = [] + for i, param in enumerate(signal.parameters): + if names: + name, tag = names[i+1] + param.name = name + options = getattr(tag, 'options', {}) + param_type = options.get(OPT_TYPE) + if param_type: + param.type = self._resolve(param_type.one(), param.type) + else: + tag = None + self._apply_annotations_param(signal, param, tag) + self._apply_annotations_return(signal, signal.retval, block) + + def _pass_read_annotations2(self, node, chain): + if isinstance(node, ast.Function): + self._apply_annotations2_function(node, chain) + return True + + def _apply_annotations2_function(self, node, chain): + # Handle virtual invokers + parent = chain[-1] if chain else None + block = self._blocks.get(node.symbol) + if not (block and parent): + return + virtual = block.get(TAG_VFUNC) + if not virtual: + return + invoker_name = virtual.value + matched = False + for vfunc in parent.virtual_methods: + if vfunc.name == invoker_name: + matched = True + vfunc.invoker = node.name + # Also merge in annotations + self._apply_annotations_callable(vfunc, [parent], block) + break + if not matched: + self._transformer.log_symbol_warning(node.symbol, + "Virtual slot %r not found for %r annotation" % (invoker_name, TAG_VFUNC)) + + def _pass_type_resolution(self, node, chain): + if isinstance(node, ast.Alias): + self._transformer.resolve_type(node.target) + if isinstance(node, ast.Callable): + for parameter in node.parameters: + self._transformer.resolve_type(parameter.type) + self._transformer.resolve_type(node.retval.type) + if isinstance(node, ast.Constant): + self._transformer.resolve_type(node.value_type) + if isinstance(node, (ast.Class, ast.Interface, ast.Record, ast.Union)): + for field in node.fields: + if field.anonymous_node: + pass + else: + self._transformer.resolve_type(field.type) + if isinstance(node, (ast.Class, ast.Interface)): + resolved_parent = None + for parent in node.parent_chain: + try: + self._transformer.resolve_type(parent) + except ValueError, e: + continue + target = self._transformer.lookup_typenode(parent) + if target: + node.parent = parent + break + for prop in node.properties: + self._transformer.resolve_type(prop.type) + for sig in node.signals: + for param in sig.parameters: + self._transformer.resolve_type(param.type) + if isinstance(node, ast.Class): + for iface in node.interfaces: + self._transformer.resolve_type(iface) + if isinstance(node, ast.Interface): + for iface in node.prerequisites: + self._transformer.resolve_type(iface) + return True + + def _resolve_quarks(self): + # self._uscore_type_names is an authoritative mapping of types + # to underscored versions, since it is based on get_type() methods; + # but only covers enums that are registered as GObject enums. + # Create a fallback mapping based on all known enums in this module. + uscore_enums = {} + for enum in self._namespace.itervalues(): + if not isinstance(enum, ast.Enum): + continue + type_name = enum.symbol + uscored = to_underscores(type_name).lower() + + uscore_enums[uscored] = enum + + no_uscore_prefixed = self._transformer.strip_identifier_or_warn(type_name) + if no_uscore_prefixed not in uscore_enums: + uscore_enums[no_uscore_prefixed] = enum + + for node in self._namespace.itervalues(): + if not isinstance(node, ast.Function): + continue + if node.retval.type.target_giname != 'GLib.Quark': + continue + short = node.symbol[:-len('_quark')] + if short == "g_io_error": + # Special case; GIOError was already taken forcing GIOErrorEnum + assert self._namespace.name == 'Gio' + enum = self._namespace.get('IOErrorEnum') + else: + enum = self._uscore_type_names.get(short) + if enum is None: + enum = uscore_enums.get(short) + if enum is not None: + enum.error_quark = node.symbol + else: + self._transformer.log_node_warning(node, +"""%s: Couldn't find corresponding enumeration""" % (node.symbol, )) + + def _split_uscored_by_type(self, uscored): + """'uscored' should be an un-prefixed uscore string. This +function searches through the namespace for the longest type which +prefixes uscored, and returns (type, suffix). Example, assuming +namespace Gtk, type is TextBuffer: + +_split_uscored_by_type(text_buffer_try_new) -> (ast.Class(TextBuffer), 'try_new')""" + node = None + count = 0 + prev_split_count = -1 + while True: + components = uscored.rsplit('_', count) + if len(components) == prev_split_count: + return None + prev_split_count = len(components) + type_string = components[0] + node = self._uscore_type_names.get(type_string) + if node: + return (node, '_'.join(components[1:])) + count += 1 + + def _pair_function(self, func): + """Check to see whether a toplevel function should be a +method or constructor of some type.""" + if func.symbol.endswith('_get_type') or func.symbol.startswith('_'): + return + (ns, subsymbol) = self._transformer.split_csymbol(func.symbol) + assert ns == self._namespace + if self._pair_constructor(func, subsymbol): + return + elif self._pair_method(func, subsymbol): + return + elif self._pair_static_method(func, subsymbol): + return + + def _uscored_identifier_for_type(self, typeval): + """Given a Type(target_giname='Foo.BarBaz'), return 'bar_baz'.""" + name = typeval.get_giname() + return to_underscores_noprefix(name).lower() + + def _pair_method(self, func, subsymbol): + if not func.parameters: + return False + first = func.parameters[0] + target = self._transformer.lookup_typenode(first.type) + if not isinstance(target, (ast.Class, ast.Interface, + ast.Record, ast.Union, + glibast.GLibBoxedOther)): + return False + + # A quick hack here...in the future we should catch C signature/GI signature + # mismatches in a general way in finaltransformer + if first.type.ctype.count('*') != 1: + return False + + uscored = self._uscored_identifier_for_type(first.type) + if not subsymbol.startswith(uscored): + return False + del func.parameters[0] + subsym_idx = func.symbol.find(subsymbol) + self._namespace.float(func) + func.name = func.symbol[(subsym_idx + len(uscored) + 1):] + target.methods.append(func) + func.is_method = True + return True + + def _pair_static_method(self, func, subsymbol): + split = self._split_uscored_by_type(subsymbol) + if split is None: + return False + (node, funcname) = split + if not isinstance(node, (ast.Class, ast.Interface, + ast.Record, ast.Union, glibast.GLibBoxedOther)): + return False + self._namespace.float(func) + func.name = funcname + node.static_methods.append(func) + + def _pair_constructor(self, func, subsymbol): + if not (func.symbol.find('_new_') >= 0 or func.symbol.endswith('_new')): + return False + target = self._transformer.lookup_typenode(func.retval.type) + if not isinstance(target, (ast.Class, ast.Record, ast.Union, glibast.GLibBoxedOther)): + return False + new_idx = func.symbol.rfind('_new') + assert (new_idx >= 0) + prefix = func.symbol[:new_idx] + split = self._split_uscored_by_type(subsymbol) + if split is None: + # TODO - need a e.g. (method) annotation + self._transformer.log_node_warning(func, + "Can't find matching type for constructor; symbol=%r" % (func.symbol, )) + return False + (origin_node, funcname) = split + if isinstance(target, ast.Class): + parent = origin_node + while parent: + if parent == target: + break + if parent.parent: + parent = self._transformer.lookup_typenode(parent.parent) + else: + parent = None + if parent is None: + self._transformer.log_node_warning(func, +"Return value is not superclass for constructor; symbol=%r constructed=%r return=%r" +% (func.symbol, str(origin_node.create_type()), str(func.retval.type))) + return False + else: + if origin_node != target: + self._transformer.log_node_warning(func, +"Constructor return type mismatch symbol=%r constructed=%r return=%r" +% (func.symbol, str(origin_node.create_type()), str(func.retval.type))) + return False + self._namespace.float(func) + func.name = funcname + func.is_constructor = True + target.constructors.append(func) + # Constructors have default return semantics + if not func.retval.transfer: + func.retval.transfer = self._get_transfer_default_return(func, func.retval) + return True + + def _pair_class_virtuals(self, node): + """Look for virtual methods from the class structure.""" + if not node.glib_type_struct: + self._transformer.log_node_warning(node, + "Failed to find class structure for %r" % (node.name, )) + return + + node_type = node.create_type() + class_struct = self._transformer.lookup_typenode(node.glib_type_struct) + + # Object class fields are assumed to be read-only + # (see also _introspect_object and transformer.py) + for field in class_struct.fields: + if isinstance(field, ast.Field): + field.writable = False + + # Loop through fields to determine which are virtual + # functions and which are signal slots by + # assuming everything that doesn't share a name + # with a known signal is a virtual slot. + for field in class_struct.fields: + if not isinstance(field.anonymous_node, ast.Callback): + continue + callback = field.anonymous_node + # Check the first parameter is the object + if len(callback.parameters) == 0: + continue + firstparam_type = callback.parameters[0].type + if firstparam_type != node_type: + continue + # Also double check we don't have a signal with this + # name. + matched_signal = False + for signal in node.signals: + if signal.name.replace('-', '_') == callback.name: + matched_signal = True + break + if matched_signal: + continue + vfunc = ast.VFunction.from_callback(callback) + vfunc.inherit_file_positions(callback) + node.virtual_methods.append(vfunc) + + # Take the set of virtual methods we found, and try + # to pair up with any matching methods using the + # name+signature. + for vfunc in node.virtual_methods: + for method in node.methods: + if method.name != vfunc.name: + continue + if method.retval.type != vfunc.retval.type: + continue + if len(method.parameters) != len(vfunc.parameters): + continue + for i in xrange(len(method.parameters)): + m_type = method.parameters[i].type + v_type = vfunc.parameters[i].type + if m_type != v_type: + continue + vfunc.invoker = method.name + # Apply any annotations we have from the invoker to + # the vfunc + block = self._blocks.get(method.symbol) + self._apply_annotations_callable(vfunc, [], block) + + def _pass3(self, node, chain): + """Pass 3 is after we've loaded GType data and performed type + closure.""" + if isinstance(node, ast.Callable): + self._pass3_callable_callbacks(node) + self._pass3_callable_throws(node) + return True + + def _pass3_callable_callbacks(self, node): + """Check to see if we have anything that looks like a + callback+user_data+GDestroyNotify set.""" + + params = node.parameters + + # First, do defaults for well-known callback types + for i, param in enumerate(params): + argnode = self._transformer.lookup_typenode(param.type) + if isinstance(argnode, ast.Callback): + if param.type.target_giname in ('Gio.AsyncReadyCallback', + 'GLib.DestroyNotify'): + param.scope = ast.PARAM_SCOPE_ASYNC + param.transfer = ast.PARAM_TRANSFER_NONE + + callback_param = None + for i, param in enumerate(params): + argnode = self._transformer.lookup_typenode(param.type) + is_destroynotify = False + if isinstance(argnode, ast.Callback): + if param.type.target_giname == 'GLib.DestroyNotify': + is_destroynotify = True + else: + callback_param = param + continue + if callback_param is None: + continue + if is_destroynotify: + callback_param.destroy_name = param.argname + callback_param.scope = ast.PARAM_SCOPE_NOTIFIED + callback_param.transfer = ast.PARAM_TRANSFER_NONE + elif (param.type.is_equiv(ast.TYPE_ANY) and + param.argname.endswith('data')): + callback_param.closure_name = param.argname + + def _pass3_callable_throws(self, node): + """Check to see if we have anything that looks like a + callback+user_data+GDestroyNotify set.""" + if not node.parameters: + return + last_param = node.parameters[-1] + # Checking type.name=='GLib.Error' generates false positives + # on methods that take a 'GError *' + if last_param.type.ctype == 'GError**': + node.parameters.pop() + node.throws = True diff --git a/giscanner/scannermain.py b/giscanner/scannermain.py index 36942826..ec2302ec 100644 --- a/giscanner/scannermain.py +++ b/giscanner/scannermain.py @@ -21,19 +21,25 @@ # import subprocess +import tempfile import optparse import os import sys -from giscanner.annotationparser import AnnotationParser, InvalidAnnotationError +from giscanner.annotationparser import AnnotationParser from giscanner.ast import Include from giscanner.cachestore import CacheStore from giscanner.dumper import compile_introspection_binary -from giscanner.glibtransformer import GLibTransformer, IntrospectionBinary -from giscanner.minixpath import myxpath, xpath_assert +from giscanner.gdumpparser import GDumpParser, IntrospectionBinary +from giscanner.minixpath import xpath_assert from giscanner.sourcescanner import SourceScanner from giscanner.shlibs import resolve_shlibs from giscanner.transformer import Transformer +from giscanner.maintransformer import MainTransformer +from giscanner.introspectablepass import IntrospectablePass +from giscanner.girparser import GIRParser +from giscanner.girwriter import GIRWriter +from giscanner.utils import files_are_identical def _get_option_parser(): parser = optparse.OptionParser('%prog [options] sources') @@ -49,6 +55,15 @@ def _get_option_parser(): parser.add_option("-i", "--include", action="append", dest="includes", default=[], help="include types for other gidls") + parser.add_option('', "--generate-typelib-tests", + action="store", dest="test_codegen", default=None, + help="Generate test code for given namespace,output.h,output.c") + parser.add_option('', "--passthrough-gir", + action="store", dest="passthrough_gir", default=None, + help="Parse and re-output the specified GIR") + parser.add_option('', "--reparse-validate", + action="store_true", dest="reparse_validate_gir", default=False, + help="After generating the GIR, re-parse it to ensure validity") parser.add_option("", "--add-include-path", action="append", dest="include_paths", default=[], help="include paths for other GIR files") @@ -73,13 +88,18 @@ def _get_option_parser(): parser.add_option("-n", "--namespace", action="store", dest="namespace_name", help=("name of namespace for this unit, also " - "used as --strip-prefix default")) + "used to compute --identifier-prefix and --symbol-prefix")) parser.add_option("", "--nsversion", action="store", dest="namespace_version", help="version of namespace for this unit") - parser.add_option("", "--strip-prefix", - action="store", dest="strip_prefix", default=None, - help="remove this prefix from objects and functions") + parser.add_option("", "--identifier-prefix", + action="append", dest="identifier_prefixes", default=[], + help="""Remove this prefix from C identifiers (structure typedefs, etc.). +May be specified multiple times. This is also used as the default for --symbol-prefix if +the latter is not specified.""") + parser.add_option("", "--symbol-prefix", + action="append", dest="symbol_prefixes", default=[], + help="Remove this prefix from C symbols (function names)") parser.add_option("", "--add-init-section", action="append", dest="init_sections", default=[], help="add extra initialization code in the introspection program") @@ -101,15 +121,9 @@ def _get_option_parser(): parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="be verbose") - parser.add_option("", "--noclosure", - action="store_true", dest="noclosure", - help="do not delete unknown types") parser.add_option("", "--typelib-xml", action="store_true", dest="typelib_xml", help="Just convert GIR to typelib XML") - parser.add_option("", "--inject", - action="store_true", dest="inject", - help="Inject additional components into GIR XML") parser.add_option("", "--xpath-assertions", action="store", dest="xpath_assertions", help="Use given file to create assertions on GIR content") @@ -136,53 +150,25 @@ def _get_option_parser(): def _error(msg): raise SystemExit('ERROR: %s' % (msg, )) -def typelib_xml_strip(path): - from giscanner.girparser import GIRParser - from giscanner.girwriter import GIRWriter - from giscanner.girparser import C_NS - from xml.etree.cElementTree import parse - - c_ns_key = '{%s}' % (C_NS, ) - - tree = parse(path) - root = tree.getroot() - for node in root.getiterator(): - for attrib in list(node.attrib): - if attrib.startswith(c_ns_key): - del node.attrib[attrib] +def passthrough_gir(path, f): parser = GIRParser() - parser.parse_tree(tree) + parser.parse(path) writer = GIRWriter(parser.get_namespace(), parser.get_shared_libraries(), - parser.get_includes()) - sys.stdout.write(writer.get_xml()) - return 0 - -def inject(path, additions, outpath): - from giscanner.girparser import GIRParser - from giscanner.girwriter import GIRWriter - from xml.etree.cElementTree import parse - - tree = parse(path) - root = tree.getroot() - injectDoc = parse(open(additions)) - for node in injectDoc.getroot(): - injectPath = node.attrib['path'] - target = myxpath(root, injectPath) - if not target: - raise ValueError("Couldn't find path %r" % (injectPath, )) - for child in node: - target.append(child) - - parser = GIRParser() - parser.parse_tree(tree) - writer = GIRWriter(parser.get_namespace(), - parser.get_shared_libraries(), - parser.get_includes()) - outf = open(outpath, 'w') - outf.write(writer.get_xml()) - outf.close() + parser.get_includes(), + parser.get_pkgconfig_packages(), + parser.get_c_includes()) + f.write(writer.get_xml()) + +def test_codegen(optstring): + (namespace, out_h_filename, out_c_filename) = optstring.split(',') + if namespace == 'Everything': + from .testcodegen import EverythingCodeGenerator + gen = EverythingCodeGenerator(out_h_filename, out_c_filename) + gen.write() + else: + raise ValueError("Invaild namespace %r" % (namespace, )) return 0 def validate(assertions, path): @@ -237,18 +223,14 @@ def scanner_main(args): parser = _get_option_parser() (options, args) = parser.parse_args(args) + if options.passthrough_gir: + passthrough_gir(options.passthrough_gir, sys.stdout) + if options.test_codegen: + return test_codegen(options.test_codegen) + if len(args) <= 1: _error('Need at least one filename') - if options.typelib_xml: - return typelib_xml_strip(args[1]) - - if options.inject: - if len(args) != 4: - _error('Need three filenames; e.g. g-ir-scanner ' - '--inject Source.gir Additions.xml SourceOut.gir') - return inject(*args[1:4]) - if options.xpath_assertions: return validate(options.xpath_assertions, args[1]) @@ -281,11 +263,9 @@ def scanner_main(args): cachestore = CacheStore() transformer = Transformer(cachestore, options.namespace_name, - options.namespace_version) - if options.strip_prefix: - transformer.set_strip_prefix(options.strip_prefix) - else: - transformer.set_strip_prefix(options.namespace_name) + options.namespace_version, + options.identifier_prefixes, + options.symbol_prefixes) if options.warn_all: transformer.enable_warnings(True) transformer.set_include_paths(options.include_paths) @@ -318,12 +298,11 @@ def scanner_main(args): # Transform the C AST nodes into higher level # GLib/GObject nodes - glibtransformer = GLibTransformer(transformer, - noclosure=options.noclosure) + gdump_parser = GDumpParser(transformer) # Do enough parsing that we have the get_type() functions to reference # when creating the introspection binary - glibtransformer.init_parse() + gdump_parser.init_parse() if options.program: args=[options.program] @@ -331,38 +310,50 @@ def scanner_main(args): binary = IntrospectionBinary(args) else: binary = compile_introspection_binary(options, - glibtransformer.get_get_type_functions()) + gdump_parser.get_get_type_functions()) shlibs = resolve_shlibs(options, binary, libraries) - glibtransformer.set_introspection_binary(binary) + gdump_parser.set_introspection_binary(binary) + gdump_parser.parse() - namespace = glibtransformer.parse() + ap = AnnotationParser(ss) + blocks = ap.parse() - ap = AnnotationParser(namespace, ss, transformer) - try: - ap.parse() - except InvalidAnnotationError, e: - raise SystemExit("ERROR in annotation: %s" % (str(e), )) + main = MainTransformer(transformer, blocks) + main.transform() - glibtransformer.final_analyze() + final = IntrospectablePass(transformer) + final.validate() if options.warn_fatal and transformer.did_warn(): - return 1 + transformer.log_warning("warnings configured as fatal", fatal=True) + # Redundant sys.exit here, just in case + sys.exit(1) # Write out AST if options.packages_export: exported_packages = options.packages_export else: exported_packages = options.packages - writer = Writer(namespace, shlibs, transformer.get_includes(), - exported_packages, options.c_includes, - transformer.get_strip_prefix()) + writer = Writer(transformer.namespace, shlibs, transformer.get_includes(), + exported_packages, options.c_includes) data = writer.get_xml() if options.output: - fd = open(options.output, "w") - fd.write(data) + tempdir = os.path.dirname(options.output) or os.getcwd() + main_f = tempfile.NamedTemporaryFile(suffix='.gir', dir=tempdir, delete=False) + main_f.write(data) + main_f.close() + if options.reparse_validate_gir: + temp_f = tempfile.NamedTemporaryFile(suffix='.gir', dir=tempdir, delete=False) + passthrough_gir(main_f.name, temp_f) + temp_f.close() + if not files_are_identical(main_f.name, temp_f.name): + raise SystemExit( +"Failed to re-parse .gir file; scanned=%r passthrough=%r" % (main_f.name, temp_f.name)) + os.unlink(temp_f.name) + os.rename(main_f.name, options.output) else: - print data + sys.stdout.write(data) return 0 diff --git a/giscanner/sourcescanner.py b/giscanner/sourcescanner.py index cd7a4321..a2db2e74 100644 --- a/giscanner/sourcescanner.py +++ b/giscanner/sourcescanner.py @@ -154,10 +154,16 @@ class SourceSymbol(object): self._symbol = symbol def __repr__(self): - return '<%s type=%r ident=%r>' % ( + src = self.source_filename + if src: + line = self.line + if line: + src += ':%r' % (line, ) + return '<%s type=%r ident=%r src=%r>' % ( self.__class__.__name__, symbol_type_name(self.type), - self.ident) + self.ident, + src) @property def const_int(self): diff --git a/giscanner/testcodegen.py b/giscanner/testcodegen.py new file mode 100644 index 00000000..f304dc7a --- /dev/null +++ b/giscanner/testcodegen.py @@ -0,0 +1,119 @@ +# -*- Mode: Python -*- +# GObject-Introspection - a framework for introspecting GObject libraries +# Copyright (C) 2010 Red Hat, Inc. +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the +# Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# + +from StringIO import StringIO +from . import ast +from .codegen import CCodeGenerator + +DEFAULT_C_VALUES = {ast.TYPE_ANY: 'NULL', + ast.TYPE_STRING: '""', + ast.TYPE_FILENAME: '""', + ast.TYPE_GTYPE: 'g_object_get_type ()'} + +def get_default_for_typeval(typeval): + default = DEFAULT_C_VALUES.get(typeval) + if default: + return default + return "0" + +def uscore_from_type(typeval): + if typeval.target_fundamental: + return typeval.target_fundamental.replace(' ', '_') + elif typeval.target_giname: + return typeval.target_giname.replace('.', '').lower() + else: + assert False, typeval + +class EverythingCodeGenerator(object): + + def __init__(self, out_h_filename, out_c_filename): + self.namespace = ast.Namespace('Everything', '1.0') + self.gen = CCodeGenerator(self.namespace, out_h_filename, out_c_filename) + + def write(self): + func = ast.Function('nullfunc', + ast.Return(ast.TYPE_NONE, transfer=ast.PARAM_TRANSFER_NONE), + [], False, self.gen.gen_symbol('nullfunc')) + self.namespace.append(func) + body = " return;\n" + self.gen.set_function_body(func, body) + + # First pass, generate constant returns + prefix = 'const return ' + for typeval in ast.INTROSPECTABLE_BASIC: + name = prefix + uscore_from_type(typeval) + sym = self.gen.gen_symbol(name) + func = ast.Function(name, + ast.Return(typeval, transfer=ast.PARAM_TRANSFER_NONE), + [], False, sym) + self.namespace.append(func) + default = get_default_for_typeval(typeval) + body = " return %s;\n" % (default, ) + self.gen.set_function_body(func, body) + + # Void return, one parameter + prefix = 'oneparam ' + for typeval in ast.INTROSPECTABLE_BASIC: + if typeval is ast.TYPE_NONE: + continue + name = prefix + uscore_from_type(typeval) + sym = self.gen.gen_symbol(name) + func = ast.Function(name, + ast.Return(ast.TYPE_NONE, transfer=ast.PARAM_TRANSFER_NONE), + [ast.Parameter('arg0', typeval, transfer=ast.PARAM_TRANSFER_NONE, + direction=ast.PARAM_DIRECTION_IN)], False, sym) + self.namespace.append(func) + self.gen.set_function_body(func, " return;\n") + + # Void return, one (out) parameter + prefix = 'one_outparam ' + for typeval in ast.INTROSPECTABLE_BASIC: + if typeval is ast.TYPE_NONE: + continue + name = prefix + uscore_from_type(typeval) + sym = self.gen.gen_symbol(name) + func = ast.Function(name, + ast.Return(ast.TYPE_NONE, transfer=ast.PARAM_TRANSFER_NONE), + [ast.Parameter('arg0', typeval, transfer=ast.PARAM_TRANSFER_NONE, + direction=ast.PARAM_DIRECTION_OUT)], False, sym) + self.namespace.append(func) + body = StringIO('w') + default = get_default_for_typeval(func.retval) + body.write(" *arg0 = %s;\n" % (default, )) + body.write(" return;\n") + self.gen.set_function_body(func, body.getvalue()) + + # Passthrough one parameter + prefix = 'passthrough_one ' + for typeval in ast.INTROSPECTABLE_BASIC: + if typeval is ast.TYPE_NONE: + continue + name = prefix + uscore_from_type(typeval) + sym = self.gen.gen_symbol(name) + func = ast.Function(name, ast.Return(typeval, transfer=ast.PARAM_TRANSFER_NONE), + [ast.Parameter('arg0', typeval, transfer=ast.PARAM_TRANSFER_NONE, + direction=ast.PARAM_DIRECTION_IN)], False, sym) + self.namespace.append(func) + body = StringIO('w') + default = get_default_for_typeval(func.retval) + body.write(" return arg0;\n") + self.gen.set_function_body(func, body.getvalue()) + + self.gen.codegen() diff --git a/giscanner/transformer.py b/giscanner/transformer.py index e2a22049..f6639a61 100644 --- a/giscanner/transformer.py +++ b/giscanner/transformer.py @@ -20,17 +20,11 @@ import os import sys +import re -from .ast import (Bitfield, Callback, Enum, Function, Namespace, Member, - Parameter, Return, Struct, Field, - Type, Array, Alias, Interface, Class, Node, Union, - Varargs, Constant, type_name_from_ctype, - type_names, TYPE_ANY, TYPE_STRING, - BASIC_GIR_TYPES) +from . import ast from .config import DATADIR, GIR_DIR, GIR_SUFFIX -from .glibast import GLibBoxed from .girparser import GIRParser -from .odict import odict from .sourcescanner import ( SourceSymbol, ctype_name, CTYPE_POINTER, CTYPE_BASIC_TYPE, CTYPE_UNION, CTYPE_ARRAY, CTYPE_TYPEDEF, @@ -39,57 +33,36 @@ from .sourcescanner import ( CSYMBOL_TYPE_ENUM, CSYMBOL_TYPE_UNION, CSYMBOL_TYPE_OBJECT, CSYMBOL_TYPE_MEMBER, CSYMBOL_TYPE_ELLIPSIS, CSYMBOL_TYPE_CONST, TYPE_QUALIFIER_CONST) -from .utils import to_underscores -_xdg_data_dirs = [x for x in os.environ.get('XDG_DATA_DIRS', '').split(':') \ - + [DATADIR, '/usr/share'] if x] - - -class SkipError(Exception): +class TypeResolutionException(Exception): pass - -class Names(object): - names = property(lambda self: self._names) - aliases = property(lambda self: self._aliases) - type_names = property(lambda self: self._type_names) - ctypes = property(lambda self: self._ctypes) - - def __init__(self): - super(Names, self).__init__() - self._names = odict() # Maps from GIName -> (namespace, node) - self._aliases = {} # Maps from GIName -> GIName - self._type_names = {} # Maps from GTName -> (namespace, node) - self._ctypes = {} # Maps from CType -> (namespace, node) - +_xdg_data_dirs = [x for x in os.environ.get('XDG_DATA_DIRS', '').split(':') \ + + [DATADIR, '/usr/share'] if x] class Transformer(object): + namespace = property(lambda self: self._namespace) - def __init__(self, cachestore, namespace_name, namespace_version): + UCASE_CONSTANT_RE = re.compile(r'[_A-Z0-9]+') + + def __init__(self, cachestore, namespace_name, namespace_version, + identifier_prefixes=None, symbol_prefixes=None): self._cwd = os.getcwd() + os.sep self._cachestore = cachestore self.generator = None - self._namespace = Namespace(namespace_name, namespace_version) - self._names = Names() + self._namespace = ast.Namespace(namespace_name, namespace_version, + identifier_prefixes=identifier_prefixes, + symbol_prefixes=symbol_prefixes) self._pkg_config_packages = set() self._typedefs_ns = {} - self._strip_prefix = '' self._enable_warnings = False self._warned = False - self._includes = set() + self._includes = {} + self._include_names = set() self._includepaths = [] - def get_names(self): - return self._names - def get_includes(self): - return self._includes - - def set_strip_prefix(self, strip_prefix): - self._strip_prefix = strip_prefix - - def get_strip_prefix(self): - return self._strip_prefix + return self._include_names def enable_warnings(self, enable): self._enable_warnings = enable @@ -103,39 +76,97 @@ class Transformer(object): def set_source_ast(self, src_ast): self.generator = src_ast + def _append_new_node(self, node): + original = self._namespace.get(node.name) + # Special case constants here; we allow duplication to sort-of + # handle #ifdef. But this introduces an arch-dependency in the .gir + # file. So far this has only come up scanning glib - in theory, other + # modules will just depend on that. + if isinstance(original, ast.Constant) and isinstance(node, ast.Constant): + pass + elif original: + positions = set() + positions.update(original.file_positions) + positions.update(node.file_positions) + self.log_warning("Namespace conflict for '%s'" % (node.name, ), + positions, fatal=True) + else: + self._namespace.append(node) + def parse(self): - nodes = [] for symbol in self.generator.get_symbols(): - try: - node = self._traverse_one(symbol) - except SkipError: - continue - self._add_node(node) - return self._namespace + node = self._traverse_one(symbol) + if node: + self._append_new_node(node) + + # Now look through the namespace for things like + # typedef struct _Foo Foo; + # where we've never seen the struct _Foo. Just create + # an empty structure for these as "disguised" + # If we do have a class/interface, merge fields + for typedef, compound in self._typedefs_ns.iteritems(): + ns_compound = self._namespace.get(compound.name) + if not ns_compound: + ns_compound = self._namespace.get('_' + compound.name) + if (not ns_compound and isinstance(compound, (ast.Record, ast.Union)) + and len(compound.fields) == 0): + disguised = ast.Record(compound.name, typedef, disguised=True) + self._namespace.append(disguised) + elif not ns_compound: + self._namespace.append(compound) + elif isinstance(ns_compound, (ast.Record, ast.Union)) and len(ns_compound.fields) == 0: + ns_compound.fields = compound.fields + self._typedefs_ns = None def set_include_paths(self, paths): self._includepaths = list(paths) def register_include(self, include): - if include in self._includes: + if include in self._include_names: return filename = self._find_include(include) self._parse_include(filename) - self._includes.add(include) + self._include_names.add(include) + + def lookup_giname(self, name): + """Given a name of the form Foo or Bar.Foo, +return the corresponding ast.Node, or None if none +available. Will throw KeyError however for unknown +namespaces.""" + if '.' not in name: + return self._namespace.get(name) + else: + (ns, name) = name.split('.', 1) + if ns == self._namespace.name: + return self._namespace.get(name) + include = self._includes[ns] + return include.get(name) + + def lookup_typenode(self, typeobj): + """Given a Type object, if it points to a giname, +calls lookup_giname() on the name. Otherwise return +None.""" + if typeobj.target_giname: + return self.lookup_giname(typeobj.target_giname) + return None # Private - def log_warning(self, text, file_positions=None, prefix=None): + def log_warning(self, text, file_positions=None, prefix=None, + fatal=False): """Log a warning, using optional file positioning information. -If the warning is related to a Node type, see log_node_warning().""" - if not self._enable_warnings: +If the warning is related to a ast.Node type, see log_node_warning().""" + if not fatal and not self._enable_warnings: return + self._warned = True + if file_positions is None or len(file_positions) == 0: target_file_positions = [('', -1, -1)] else: target_file_positions = file_positions + position_strings = [] for (filename, line, column) in target_file_positions: if filename.startswith(self._cwd): filename = filename[len(self._cwd):] @@ -145,41 +176,58 @@ If the warning is related to a Node type, see log_node_warning().""" position = '%s:%d' % (filename, line, ) else: position = '%s:' % (filename, ) + position_strings.append(position) + for position in position_strings[:-1]: + print >>sys.stderr, "%s:" % (position, ) + last_position = position_strings[-1] + error_type = 'error' if fatal else 'warning' if prefix: print >>sys.stderr, \ -'''%s: warning: %s: %s: %s''' % (position, self._namespace.name, +'''%s: %s: %s: %s: %s''' % (last_position, error_type, self._namespace.name, prefix, text) else: print >>sys.stderr, \ -'''%s: warning: %s: %s''' % (position, self._namespace.name, text) +'''%s: %s: %s: %s''' % (last_position, error_type, self._namespace.name, text) + if fatal: + sys.exit(1) - def log_symbol_warning(self, symbol, text): + def log_symbol_warning(self, symbol, text, **kwargs): """Log a warning in the context of the given symbol.""" - file_positions = [(symbol.source_filename, symbol.line, -1)] + if symbol.source_filename: + file_positions = [(symbol.source_filename, symbol.line, -1)] + else: + file_positions = None prefix = "symbol=%r" % (symbol.ident, ) - self.log_warning(text, file_positions, prefix=prefix) + self.log_warning(text, file_positions, prefix=prefix, **kwargs) - def log_node_warning(self, node, text, context=None): + def log_node_warning(self, node, text, context=None, fatal=False): """Log a warning, using information about file positions from the given node. The optional context argument, if given, should be -another Node type which will also be displayed. If no file position +another ast.Node type which will also be displayed. If no file position information is available from the node, the position data from the context will be used.""" - if len(node.file_positions) == 0 and \ - (context is not None) and len(context.file_positions) > 0: - file_positions = context.file_positions + if hasattr(node, 'file_positions'): + if (len(node.file_positions) == 0 and + (context is not None) and len(context.file_positions) > 0): + file_positions = context.file_positions + else: + file_positions = node.file_positions else: - file_positions = node.file_positions + file_positions = None + if not context: + text = "context=%r %s" % (node, text) if context: - if isinstance(context, Function): + if isinstance(context, ast.Function): name = context.symbol else: name = context.name text = "%s: %s" % (name, text) + elif len(file_positions) == 0 and hasattr(node, 'name'): + text = "(%s)%s: %s" % (node.__class__.__name__, node.name, text) - self.log_warning(text, file_positions) + self.log_warning(text, file_positions, fatal=fatal) def _find_include(self, include): searchdirs = self._includepaths[:] @@ -200,7 +248,6 @@ context will be used.""" parser = self._cachestore.load(filename) if parser is None: parser = GIRParser() - parser.set_include_parsing(True) parser.parse(filename) self._cachestore.store(filename, parser) @@ -210,49 +257,95 @@ context will be used.""" for pkg in parser.get_pkgconfig_packages(): self._pkg_config_packages.add(pkg) namespace = parser.get_namespace() - nsname = namespace.name - for node in namespace.nodes: - if isinstance(node, Alias): - self._names.aliases[node.name] = (nsname, node) - elif isinstance(node, (GLibBoxed, Interface, Class)): - self._names.type_names[node.type_name] = (nsname, node) - giname = '%s.%s' % (nsname, node.name) - self._names.names[giname] = (nsname, node) - if hasattr(node, 'ctype'): - self._names.ctypes[node.ctype] = (nsname, node) - elif hasattr(node, 'symbol'): - self._names.ctypes[node.symbol] = (nsname, node) - - def _add_node(self, node): - if node is None: - return - if node.name.startswith('_'): - return - self._namespace.nodes.append(node) - self._names.names[node.name] = (None, node) - - def _strip_namespace_func(self, name): - prefix = self._namespace.name.lower() + '_' - if name.lower().startswith(prefix): - name = name[len(prefix):] - else: - prefix = to_underscores(self._namespace.name).lower() + '_' - if name.lower().startswith(prefix): - name = name[len(prefix):] - return self.remove_prefix(name, isfunction=True) - - def remove_prefix(self, name, isfunction=False): - # when --strip-prefix=g: - # GHashTable -> HashTable - # g_hash_table_new -> hash_table_new - prefix = self._strip_prefix.lower() - if isfunction: - prefix += '_' - if len(name) > len(prefix) and name.lower().startswith(prefix): - name = name[len(prefix):] - - while name.startswith('_'): - name = name[1:] + self._includes[namespace.name] = namespace + + def _iter_namespaces(self): + """Return an iterator over all included namespaces; the +currently-scanned namespace is first.""" + yield self._namespace + for ns in self._includes.itervalues(): + yield ns + + def _sort_matches(self, x, y): + if x[0] is self._namespace: + return 1 + elif y[0] is self._namespace: + return -1 + return cmp(x[2], y[2]) + + def split_ctype_namespaces(self, ident): + """Given a StudlyCaps string identifier like FooBar, return a +list of (namespace, stripped_identifier) sorted by namespace length, +or raise ValueError. As a special case, if the current namespace matches, +it is always biggest (i.e. last).""" + matches = [] + for ns in self._iter_namespaces(): + for prefix in ns.identifier_prefixes: + if ident.startswith(prefix): + matches.append((ns, ident[len(prefix):], len(prefix))) + break + if matches: + matches.sort(self._sort_matches) + return map(lambda x: (x[0], x[1]), matches) + raise ValueError("Unknown namespace for identifier %r" % (ident, )) + + def split_csymbol(self, symbol): + """Given a C symbol like foo_bar_do_baz, return a pair of +(namespace, stripped_symbol) or raise ValueError.""" + matches = [] + for ns in self._iter_namespaces(): + for prefix in ns.symbol_prefixes: + if not prefix.endswith('_'): + prefix = prefix + '_' + if symbol.startswith(prefix): + matches.append((ns, symbol[len(prefix):], len(prefix))) + break + if matches: + matches.sort(self._sort_matches) + return (matches[-1][0], matches[-1][1]) + raise ValueError("Unknown namespace for symbol %r" % (symbol, )) + + def strip_identifier_or_warn(self, ident, fatal=False): + hidden = ident.startswith('_') + if hidden: + ident = ident[1:] + try: + matches = self.split_ctype_namespaces(ident) + except ValueError, e: + self.log_warning(str(e), fatal=fatal) + return None + for ns, name in matches: + if ns is self._namespace: + if hidden: + return '_' + name + return name + (ns, name) = matches[-1] + self.log_warning("Skipping foreign identifier %r from namespace %s" % (ident, ns.name, ), + fatal=fatal) + return None + + def _strip_symbol_or_warn(self, symbol, is_constant=False, fatal=False): + ident = symbol.ident + if is_constant: + # Temporarily lowercase + ident = ident.lower() + hidden = ident.startswith('_') + if hidden: + ident = ident[1:] + try: + (ns, name) = self.split_csymbol(ident) + except ValueError, e: + self.log_symbol_warning(symbol, "Unknown namespace", fatal=fatal) + return None + if ns != self._namespace: + self.log_symbol_warning(symbol, +"Skipping foreign symbol from namespace %s" % (ns.name, ), + fatal=fatal) + return None + if is_constant: + name = name.upper() + if hidden: + return '_' + name return name def _traverse_one(self, symbol, stype=None): @@ -268,17 +361,17 @@ context will be used.""" return self._create_struct(symbol) elif stype == CSYMBOL_TYPE_ENUM: return self._create_enum(symbol) - elif stype == CSYMBOL_TYPE_OBJECT: - return self._create_object(symbol) elif stype == CSYMBOL_TYPE_MEMBER: return self._create_member(symbol) elif stype == CSYMBOL_TYPE_UNION: return self._create_union(symbol) elif stype == CSYMBOL_TYPE_CONST: return self._create_const(symbol) + # Ignore variable declarations in the header + elif stype == CSYMBOL_TYPE_OBJECT: + pass else: - raise NotImplementedError( - 'Transformer: unhandled symbol: %r' % (symbol, )) + print 'transformer: unhandled symbol: %r' % (symbol, ) def _enum_common_prefix(self, symbol): def common_prefix(a, b): @@ -316,86 +409,31 @@ context will be used.""" # Ok, the enum members don't have a consistent prefix # among them, so let's just remove the global namespace # prefix. - name = self.remove_prefix(child.ident) - members.append(Member(name.lower(), + name = self._strip_symbol_or_warn(child, is_constant=True) + if name is None: + return None + members.append(ast.Member(name.lower(), child.const_int, child.ident)) - enum_name = self.remove_prefix(symbol.ident) + enum_name = self.strip_identifier_or_warn(symbol.ident) + if not enum_name: + return None if symbol.base_type.is_bitfield: - klass = Bitfield + klass = ast.Bitfield else: - klass = Enum + klass = ast.Enum node = klass(enum_name, symbol.ident, members) node.add_symbol_reference(symbol) - self._names.type_names[symbol.ident] = (None, node) - return node - - def _create_object(self, symbol): - node = Member(symbol.ident, symbol.base_type.name, - symbol.ident) - node.add_symbol_reference(symbol) return node - def _type_is_callback(self, type): - if isinstance(type, Callback): - return True - node = self._names.names.get(type.name) - if node and isinstance(node[1], Callback): - return True - return False - - def _handle_closure(self, param, closure_idx, closure_param): - if (closure_param.type.name == TYPE_ANY and - closure_param.name.endswith('data')): - param.closure_name = closure_param.name - param.closure_index = closure_idx - return True - return False - - def _handle_destroy(self, param, destroy_idx, destroy_param): - if (destroy_param.type.name == 'GLib.DestroyNotify' or - destroy_param.type.ctype == 'GDestroyNotify'): - param.destroy_name = destroy_param.name - param.destroy_index = destroy_idx - return True - return False - - def _augment_callback_params(self, params): - for i, param in enumerate(params): - if not self._type_is_callback(param.type): - continue - - # set a default scope - if param.scope is None: - param.scope = 'call' - - # j is the index where we look for closure/destroy to - # group with the callback param - j = i + 1 - if j == len(params): - continue # no more args -> nothing to group - # look at the param directly following for either a - # closure or a destroy; only one of these will fire - had_closure = self._handle_closure(param, j, params[j]) - had_destroy = self._handle_destroy(param, j, params[j]) - j += 1 - # are we out of params, or did we find neither? - if j == len(params) or (not had_closure and not had_destroy): - continue - # we found either a closure or a destroy; check the - # parameter following for the other - if not had_closure: - self._handle_closure(param, j, params[j]) - if not had_destroy: - self._handle_destroy(param, j, params[j]) - def _create_function(self, symbol): parameters = list(self._create_parameters(symbol.base_type)) return_ = self._create_return(symbol.base_type.base_type) - self._augment_callback_params(parameters) - name = self._strip_namespace_func(symbol.ident) - func = Function(name, return_, parameters, symbol.ident) + name = self._strip_symbol_or_warn(symbol) + if not name: + return None + func = ast.Function(name, return_, parameters, False, symbol.ident) func.add_symbol_reference(symbol) return func @@ -413,11 +451,10 @@ context will be used.""" elif source_type.type == CTYPE_POINTER: value = self._create_source_type(source_type.base_type) + '*' else: - value = TYPE_ANY + value = 'gpointer' return value def _create_parameters(self, base_type): - # warn if we see annotations for unknown parameters param_names = set(child.ident for child in base_type.child_list) for child in base_type.child_list: @@ -427,7 +464,7 @@ context will be used.""" source_type = symbol.base_type if (source_type.type == CTYPE_POINTER and symbol.base_type.base_type.type == CTYPE_FUNCTION): - node = self._create_callback(symbol) + node = self._create_callback(symbol, member=True) elif source_type.type == CTYPE_STRUCT and source_type.name is None: node = self._create_struct(symbol, anonymous=True) elif source_type.type == CTYPE_UNION and source_type.name is None: @@ -442,21 +479,18 @@ context will be used.""" derefed_name = canonical_ctype[:-1] else: derefed_name = canonical_ctype - derefed_name = self.resolve_param_type(derefed_name) - ftype = Array(None, ctype, self.parse_ctype(derefed_name)) + ftype = ast.Array(None, self.create_type_from_ctype_string(ctype), + ctype=derefed_name) child_list = list(symbol.base_type.child_list) ftype.zeroterminated = False if child_list: - ftype.size = '%d' % (child_list[0].const_int, ) + ftype.size = child_list[0].const_int else: - ftype = self._create_type(symbol.base_type, - is_param=False, is_retval=False) - ftype = self.resolve_param_type(ftype) - # Fields are assumed to be read-write + ftype = self._create_type_from_base(symbol.base_type) + # ast.Fields are assumed to be read-write # (except for Objects, see also glibtransformer.py) - node = Field(symbol.ident, ftype, ftype.name, + node = ast.Field(symbol.ident, ftype, readable=True, writable=True, bits=symbol.const_int) - node.add_symbol_reference(symbol) return node def _create_typedef(self, symbol): @@ -477,14 +511,16 @@ context will be used.""" CTYPE_POINTER, CTYPE_BASIC_TYPE, CTYPE_VOID): - name = self.remove_prefix(symbol.ident) + name = self.strip_identifier_or_warn(symbol.ident) + if not name: + return None if symbol.base_type.name: - target = self.remove_prefix(symbol.base_type.name) + target = self.create_type_from_ctype_string(symbol.base_type.name) else: - target = 'none' - if name in type_names: + target = ast.TYPE_ANY + if name in ast.type_names: return None - return Alias(name, target, ctype=symbol.ident) + return ast.Alias(name, target, ctype=symbol.ident) else: raise NotImplementedError( "symbol %r of type %s" % (symbol.ident, ctype_name(ctype))) @@ -494,22 +530,20 @@ context will be used.""" # First look up the ctype including any pointers; # a few type names like 'char*' have their own aliases # and we need pointer information for those. - firstpass = type_name_from_ctype(ctype) + firstpass = ast.type_names.get(ctype) # If we have a particular alias for this, skip deep # canonicalization to prevent changing # e.g. char* -> int8* - if firstpass != ctype: - return firstpass + if firstpass: + return firstpass.target_fundamental - # We're also done if the type is already a fundamental - # known type, or there are no pointers. - if ctype in type_names or not firstpass.endswith('*'): - return firstpass + if not ctype.endswith('*'): + return ctype # We have a pointer type. # Strip the end pointer, canonicalize our base type - base = firstpass[:-1] + base = ctype[:-1] canonical_base = self._canonicalize_ctype(base) # Append the pointer again @@ -527,52 +561,71 @@ context will be used.""" # Preserve "pointerness" of struct/union members if (is_member and canonical.endswith('*') and - derefed_typename in BASIC_GIR_TYPES): - return TYPE_ANY + derefed_typename in ast.basic_type_names): + return 'gpointer' else: return derefed_typename - def _create_type(self, source_type, is_param, is_retval): + def _create_type_from_base(self, source_type, is_parameter=False, is_return=False): ctype = self._create_source_type(source_type) - if ctype.startswith('va_list'): - raise SkipError() - # FIXME: FILE* should not be skipped, it should be handled - # properly instead - elif ctype == 'FILE*': - raise SkipError - - is_member = not (is_param or is_retval) - # Here we handle basic type parsing; most of the heavy lifting - # and inference comes in annotationparser.py when we merge - # in annotation data. - derefed_name = self.parse_ctype(ctype, is_member) - rettype = Type(derefed_name, ctype) - rettype.canonical = self._canonicalize_ctype(ctype) - derefed_ctype = ctype.replace('*', '') - rettype.derefed_canonical = self._canonicalize_ctype(derefed_ctype) - - canontype = type_name_from_ctype(ctype) - # Is it a const char * or a const gpointer? - if ((canontype == TYPE_STRING or source_type.type == CTYPE_POINTER) and - (source_type.base_type.type_qualifier & TYPE_QUALIFIER_CONST)): - rettype.is_const = True - return rettype + const = ((source_type.type == CTYPE_POINTER) and + (source_type.base_type.type_qualifier & TYPE_QUALIFIER_CONST)) + return self.create_type_from_ctype_string(ctype, is_const=const, + is_parameter=is_parameter, is_return=is_return) + + def _create_bare_container_type(self, base, ctype=None, + is_const=False): + if base in ('GList', 'GSList', 'GLib.List', 'GLib.SList'): + if base in ('GList', 'GSList'): + name = 'GLib.' + base[1:] + else: + name = base + return ast.List(name, ast.TYPE_ANY, ctype=ctype, + is_const=is_const) + elif base in ('GArray', 'GPtrArray', 'GByteArray', + 'GLib.Array', 'GLib.PtrArray', 'GLib.ByteArray'): + if base in ('GArray', 'GPtrArray', 'GByteArray'): + name = 'GLib.' + base[1:] + else: + name = base + return ast.Array(name, ast.TYPE_ANY, ctype=ctype, + is_const=is_const) + elif base in ('GHashTable', 'GLib.HashTable'): + return ast.Map(ast.TYPE_ANY, ast.TYPE_ANY, ctype=ctype, is_const=is_const) + return None + + def create_type_from_ctype_string(self, ctype, is_const=False, + is_parameter=False, is_return=False): + canonical = self._canonicalize_ctype(ctype) + base = canonical.replace('*', '') + + # Special default: char ** -> ast.Array, same for GStrv + if (is_return and canonical == 'utf8*') or base == 'GStrv': + bare_utf8 = ast.TYPE_STRING.clone() + bare_utf8.ctype = None + return ast.Array(None, bare_utf8, ctype=ctype, + is_const=is_const) + + fundamental = ast.type_names.get(base) + if fundamental is not None: + return ast.Type(target_fundamental=fundamental.target_fundamental, + ctype=ctype, + is_const=is_const) + container = self._create_bare_container_type(base, ctype=ctype, is_const=is_const) + if container: + return container + return ast.Type(ctype=ctype, is_const=is_const) def _create_parameter(self, symbol): if symbol.type == CSYMBOL_TYPE_ELLIPSIS: - ptype = Varargs() + ptype = ast.Varargs() else: - ptype = self._create_type(symbol.base_type, - is_param=True, is_retval=False) - ptype = self.resolve_param_type(ptype) - return Parameter(symbol.ident, ptype) + ptype = self._create_type_from_base(symbol.base_type, is_parameter=True) + return ast.Parameter(symbol.ident, ptype) def _create_return(self, source_type): - rtype = self._create_type(source_type, - is_param=False, is_retval=True) - rtype = self.resolve_param_type(rtype) - return_ = Return(rtype) - return return_ + typeval = self._create_type_from_base(source_type, is_return=True) + return ast.Return(typeval) def _create_const(self, symbol): # Don't create constants for non-public things @@ -580,44 +633,67 @@ context will be used.""" if (symbol.source_filename is None or not symbol.source_filename.endswith('.h')): return None - name = self._strip_namespace_func(symbol.ident) + # ignore non-uppercase defines + if not self.UCASE_CONSTANT_RE.match(symbol.ident): + return None + name = self._strip_symbol_or_warn(symbol, is_constant=True) + if not name: + return None if symbol.const_string is not None: - type_name = 'utf8' + typeval = ast.TYPE_STRING value = symbol.const_string elif symbol.const_int is not None: - type_name = 'gint' - value = symbol.const_int + typeval = ast.TYPE_INT + value = '%d' % (symbol.const_int, ) elif symbol.const_double is not None: - type_name = 'gdouble' - value = symbol.const_double + typeval = ast.TYPE_DOUBLE + value = '%f' % (symbol.const_double, ) else: raise AssertionError() - const = Constant(name, type_name, value) + const = ast.Constant(name, typeval, value) const.add_symbol_reference(symbol) return const def _create_typedef_struct(self, symbol, disguised=False): - name = self.remove_prefix(symbol.ident) - struct = Struct(name, symbol.ident, disguised) + name = self.strip_identifier_or_warn(symbol.ident) + if not name: + return None + struct = ast.Record(name, symbol.ident, disguised) + self._parse_fields(symbol, struct) struct.add_symbol_reference(symbol) self._typedefs_ns[symbol.ident] = struct - self._create_struct(symbol) - return struct + return None def _create_typedef_union(self, symbol): - name = self.remove_prefix(symbol.ident) - union = Union(name, symbol.ident) + name = self.strip_identifier_or_warn(symbol.ident) + if not name: + return None + union = ast.Union(name, symbol.ident) + self._parse_fields(symbol, union) union.add_symbol_reference(symbol) self._typedefs_ns[symbol.ident] = union - self._create_union(symbol) - return union + return None def _create_typedef_callback(self, symbol): callback = self._create_callback(symbol) + if not callback: + return None self._typedefs_ns[callback.name] = callback return callback + def _parse_fields(self, symbol, compound): + for child in symbol.base_type.child_list: + child_node = self._traverse_one(child) + if not child_node: + continue + if isinstance(child_node, ast.Field): + field = child_node + else: + field = ast.Field(child.ident, None, True, False, + anonymous_node=child_node) + compound.fields.append(field) + def _create_compound(self, klass, symbol, anonymous): if symbol.ident is None: # the compound is an anonymous member of another union or a struct @@ -631,100 +707,95 @@ context will be used.""" # to resolve through the typedefs to find the real # name if symbol.ident.startswith('_'): - name = symbol.ident[1:] - compound = self._typedefs_ns.get(name, None) - else: - name = symbol.ident + compound = self._typedefs_ns.get(symbol.ident[1:], None) if compound is None: - name = self.remove_prefix(name) + if anonymous: + name = symbol.ident + else: + name = self.strip_identifier_or_warn(symbol.ident) + if not name: + return None compound = klass(name, symbol.ident) - for child in symbol.base_type.child_list: - field = self._traverse_one(child) - if field: - compound.fields.append(field) - + self._parse_fields(symbol, compound) compound.add_symbol_reference(symbol) return compound def _create_struct(self, symbol, anonymous=False): - return self._create_compound(Struct, symbol, anonymous) + return self._create_compound(ast.Record, symbol, anonymous) def _create_union(self, symbol, anonymous=False): - return self._create_compound(Union, symbol, anonymous) + return self._create_compound(ast.Union, symbol, anonymous) - def _create_callback(self, symbol): + def _create_callback(self, symbol, member=False): parameters = list(self._create_parameters(symbol.base_type.base_type)) retval = self._create_return(symbol.base_type.base_type.base_type) # Mark the 'user_data' arguments for i, param in enumerate(parameters): - if (param.type.name == TYPE_ANY and - param.name == 'user_data'): - param.closure_index = i - - if symbol.ident.find('_') > 0: - name = self.remove_prefix(symbol.ident, True) + if (param.type.target_fundamental == 'gpointer' and + param.argname == 'user_data'): + param.closure_name = param.argname + + if member: + name = symbol.ident + elif symbol.ident.find('_') > 0: + name = self._strip_symbol_or_warn(symbol) + if not name: + return None else: - name = self.remove_prefix(symbol.ident) - callback = Callback(name, retval, parameters, symbol.ident) + name = self.strip_identifier_or_warn(symbol.ident) + if not name: + return None + callback = ast.Callback(name, retval, parameters, False) callback.add_symbol_reference(symbol) return callback + def create_type_from_user_string(self, typestr): + """Parse a C type string (as might be given from an + annotation) and resolve it. For compatibility, we can consume +both GI type string (utf8, Foo.Bar) style, as well as C (char *, FooBar) style.""" + if '.' in typestr: + container = self._create_bare_container_type(typestr) + if container: + return container + return self._namespace.type_from_name(typestr) + typeval = self.create_type_from_ctype_string(typestr) + self.resolve_type(typeval) + # Explicitly clear out the c_type; there isn't one in this case. + typeval.ctype = None + return typeval + + def resolve_type(self, typeval): + if isinstance(typeval, (ast.Array, ast.List)): + self.resolve_type(typeval.element_type) + return + elif isinstance(typeval, ast.Map): + self.resolve_type(typeval.key_type) + self.resolve_type(typeval.value_type) + return + elif not typeval.resolved and typeval.ctype: + pointer_stripped = typeval.ctype.replace('*', '') + try: + matches = self.split_ctype_namespaces(pointer_stripped) + except ValueError, e: + raise TypeResolutionException(e) + target_giname=None + for namespace, name in matches: + target = namespace.get(name) + if not target: + target = namespace.get_by_ctype(pointer_stripped) + if target: + typeval.target_giname='%s.%s' % (namespace.name, target.name) + return + def _typepair_to_str(self, item): nsname, item = item if nsname is None: return item.name return '%s.%s' % (nsname, item.name) - def _resolve_type_name_1(self, type_name, ctype, names): - # First look using the built-in names - if ctype: - try: - return type_names[ctype] - except KeyError, e: - pass - try: - return type_names[type_name] - except KeyError, e: - pass - - if ctype: - ctype = ctype.replace('*', '') - resolved = names.ctypes.get(ctype) - if resolved: - return self._typepair_to_str(resolved) - type_name = self.remove_prefix(type_name) - resolved = names.aliases.get(type_name) - if resolved: - return self._typepair_to_str(resolved) - resolved = names.names.get(type_name) - if resolved: - return self._typepair_to_str(resolved) - resolved = names.type_names.get(type_name) - if resolved: - return self._typepair_to_str(resolved) - raise KeyError("failed to find %r" % (type_name, )) - - def resolve_type_name_full(self, type_name, ctype, - names, allow_invalid=True): - try: - return self._resolve_type_name_1(type_name, ctype, names) - except KeyError, e: - try: - return self._resolve_type_name_1(type_name, ctype, self._names) - except KeyError, e: - if not allow_invalid: - raise - return type_name - - def resolve_type_name(self, type_name, ctype=None): - try: - return self.resolve_type_name_full(type_name, ctype, self._names) - except KeyError, e: - return type_name - def gtypename_to_giname(self, gtname, names): resolved = names.type_names.get(gtname) if resolved: @@ -742,23 +813,6 @@ context will be used.""" else: return None - def resolve_param_type_full(self, ptype, names, **kwargs): - if isinstance(ptype, Node): - ptype.name = self.resolve_type_name_full(ptype.name, - self.ctype_of(ptype), - names, **kwargs) - elif isinstance(ptype, basestring): - return self.resolve_type_name_full(ptype, ptype, names, **kwargs) - else: - raise AssertionError("Unhandled param: %r" % (ptype, )) - return ptype - - def resolve_param_type(self, ptype): - try: - return self.resolve_param_type_full(ptype, self._names) - except KeyError, e: - return ptype - def follow_aliases(self, type_name, names): while True: resolved = names.aliases.get(type_name) @@ -768,8 +822,3 @@ context will be used.""" else: break return type_name - - def iter_enums(self): - for node in self._namespace.nodes: - if isinstance(node, Enum): - yield node diff --git a/giscanner/utils.py b/giscanner/utils.py index 1bd23fc0..dba958ef 100644 --- a/giscanner/utils.py +++ b/giscanner/utils.py @@ -102,3 +102,16 @@ def get_libtool_command(options): return None return ['libtool'] + + +def files_are_identical(path1, path2): + f1 = open(path1) + f2 = open(path2) + buf1 = f1.read(8192) + buf2 = f2.read(8192) + while buf1 == buf2 and buf1 != '': + buf1 = f1.read(8192) + buf2 = f2.read(8192) + f1.close() + f2.close() + return buf1 == buf2 -- cgit v1.2.1