/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
 * GObject introspection: A parser for the XML GIR format
 *
 * Copyright (C) 2005 Matthias Clasen
 * Copyright (C) 2008 Philip Van Hoof
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * 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.
 */

#include "config.h"

#include "girparser-private.h"

#include "girnode-private.h"
#include "gitypelib-internal.h"
#include "glib-private.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>       /* For time_t */
#include <sys/types.h>  /* For off_t on both Unix and Windows */

#ifdef G_OS_UNIX
#include <sys/socket.h> /* For socklen_t */
#endif

/* This is a "major" version in the sense that it's only bumped
 * for incompatible changes.
 */
#define SUPPORTED_GIR_VERSION "1.2"

#ifdef G_OS_WIN32

#include <windows.h>

#ifdef GIR_DIR
#undef GIR_DIR
#endif

/* GIR_DIR is used only in code called just once,
 * so no problem leaking this
 */
#define GIR_DIR \
  g_build_filename (g_win32_get_package_installation_directory_of_module(NULL), \
    "share", \
    GIR_SUFFIX, \
    NULL)
#endif

struct _GIIrParser
{
  char **includes;
  char **gi_gir_path;
  GList *parsed_modules; /* All previously parsed modules */
  GLogLevelFlags logged_levels;
};

typedef enum
{
  STATE_NONE = 0,
  STATE_START,
  STATE_END,
  STATE_REPOSITORY,
  STATE_INCLUDE,
  STATE_C_INCLUDE,     /* 5 */
  STATE_PACKAGE,
  STATE_NAMESPACE,
  STATE_ENUM,
  STATE_BITFIELD,
  STATE_FUNCTION,      /* 10 */
  STATE_FUNCTION_RETURN,
  STATE_FUNCTION_PARAMETERS,
  STATE_FUNCTION_PARAMETER,
  STATE_CLASS,
  STATE_CLASS_FIELD,   /* 15 */
  STATE_CLASS_PROPERTY,
  STATE_INTERFACE,
  STATE_INTERFACE_PROPERTY,
  STATE_INTERFACE_FIELD,
  STATE_IMPLEMENTS,    /* 20 */
  STATE_PREREQUISITE,
  STATE_BOXED,
  STATE_BOXED_FIELD,
  STATE_STRUCT,
  STATE_STRUCT_FIELD,  /* 25 */
  STATE_UNION,
  STATE_UNION_FIELD,
  STATE_NAMESPACE_CONSTANT,
  STATE_CLASS_CONSTANT,
  STATE_INTERFACE_CONSTANT,  /* 30 */
  STATE_ALIAS,
  STATE_TYPE,
  STATE_ATTRIBUTE,
  STATE_PASSTHROUGH
} ParseState;

typedef struct _ParseContext ParseContext;
struct _ParseContext
{
  GIIrParser *parser;

  ParseState state;
  int unknown_depth;
  ParseState prev_state;

  GList *modules;
  GList *include_modules;
  GPtrArray *dependencies;
  GHashTable *aliases;
  GHashTable *disguised_structures;
  GHashTable *pointer_structures;

  const char *file_path;
  const char *namespace;
  const char *c_prefix;
  GIIrModule *current_module;
  GSList *node_stack;
  char *current_alias;
  GIIrNode *current_typed;
  GList *type_stack;
  GList *type_parameters;
  int type_depth;
  ParseState in_embedded_state;
};
#define CURRENT_NODE(ctx) ((GIIrNode *)((ctx)->node_stack->data))

static void start_element_handler (GMarkupParseContext  *context,
                                   const char           *element_name,
                                   const char          **attribute_names,
                                   const char          **attribute_values,
                                   void                 *user_data,
                                   GError              **error);
static void end_element_handler   (GMarkupParseContext  *context,
                                   const char           *element_name,
                                   void                 *user_data,
                                   GError              **error);
static void text_handler          (GMarkupParseContext  *context,
                                   const char           *text,
                                   gsize                 text_len,
                                   void                 *user_data,
                                   GError              **error);
static void cleanup               (GMarkupParseContext *context,
                                   GError              *error,
                                   void                *user_data);
static void state_switch (ParseContext *ctx, ParseState newstate);


static GMarkupParser markup_parser =
{
  start_element_handler,
  end_element_handler,
  text_handler,
  NULL,
  cleanup
};

static gboolean
start_alias (GMarkupParseContext  *context,
             const char           *element_name,
             const char          **attribute_names,
             const char          **attribute_values,
             ParseContext         *ctx,
             GError              **error);
static gboolean
start_type (GMarkupParseContext  *context,
            const char           *element_name,
            const char          **attribute_names,
            const char          **attribute_values,
            ParseContext         *ctx,
            GError              **error);

static const char *find_attribute (const char  *name,
                                   const char **attribute_names,
                                   const char **attribute_values);


GIIrParser *
gi_ir_parser_new (void)
{
  GIIrParser *parser = g_slice_new0 (GIIrParser);
  const char *gi_gir_path = g_getenv ("GI_GIR_PATH");

  if (gi_gir_path != NULL)
    parser->gi_gir_path = g_strsplit (gi_gir_path, G_SEARCHPATH_SEPARATOR_S, 0);

  parser->logged_levels = G_LOG_LEVEL_MASK & ~(G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_DEBUG);
  return parser;
}

void
gi_ir_parser_set_debug (GIIrParser     *parser,
                        GLogLevelFlags  logged_levels)
{
  parser->logged_levels = logged_levels;
}

void
gi_ir_parser_free (GIIrParser *parser)
{
  g_strfreev (parser->includes);
  g_strfreev (parser->gi_gir_path);

  g_clear_list (&parser->parsed_modules, (GDestroyNotify) gi_ir_module_free);

  g_slice_free (GIIrParser, parser);
}

void
gi_ir_parser_set_includes (GIIrParser         *parser,
                           const char *const *includes)
{
  g_strfreev (parser->includes);

  parser->includes = g_strdupv ((char **)includes);
}

static void
firstpass_start_element_handler (GMarkupParseContext  *context,
                                 const char           *element_name,
                                 const char          **attribute_names,
                                 const char          **attribute_values,
                                 void                 *user_data,
                                 GError              **error)
{
  ParseContext *ctx = user_data;

  if (strcmp (element_name, "alias") == 0)
    {
      start_alias (context, element_name, attribute_names, attribute_values,
                   ctx, error);
    }
  else if (ctx->state == STATE_ALIAS && strcmp (element_name, "type") == 0)
    {
      start_type (context, element_name, attribute_names, attribute_values,
                  ctx, error);
    }
  else if (strcmp (element_name, "record") == 0)
    {
      const char *name;
      const char *disguised;
      const char *pointer;

      name = find_attribute ("name", attribute_names, attribute_values);
      disguised = find_attribute ("disguised", attribute_names, attribute_values);
      pointer = find_attribute ("pointer", attribute_names, attribute_values);

      if (g_strcmp0 (pointer, "1") == 0)
        {
          char *key;

          key = g_strdup_printf ("%s.%s", ctx->namespace, name);
          g_hash_table_replace (ctx->pointer_structures, key, GINT_TO_POINTER (1));
        }
      else if (g_strcmp0 (disguised, "1") == 0)
        {
          char *key;

          key = g_strdup_printf ("%s.%s", ctx->namespace, name);
          g_hash_table_replace (ctx->disguised_structures, key, GINT_TO_POINTER (1));
        }
    }
}

static void
firstpass_end_element_handler (GMarkupParseContext  *context,
                               const char           *element_name,
                               gpointer              user_data,
                               GError              **error)
{
  ParseContext *ctx = user_data;
  if (strcmp (element_name, "alias") == 0)
    {
      state_switch (ctx, STATE_NAMESPACE);
      g_free (ctx->current_alias);
      ctx->current_alias = NULL;
    }
  else if (strcmp (element_name, "type") == 0 && ctx->state == STATE_TYPE)
    state_switch (ctx, ctx->prev_state);
}

static GMarkupParser firstpass_parser =
{
  firstpass_start_element_handler,
  firstpass_end_element_handler,
  NULL,
  NULL,
  NULL,
};

static char *
locate_gir (GIIrParser *parser,
            const char *girname)
{
  const char *const *datadirs;
  const char *const *dir;
  char *path = NULL;

  g_debug ("Looking for %s", girname);
  datadirs = g_get_system_data_dirs ();

  if (parser->includes != NULL)
    {
      for (dir = (const char *const *)parser->includes; *dir; dir++)
        {
          path = g_build_filename (*dir, girname, NULL);
          g_debug ("Trying %s from includes", path);
          if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
            return g_steal_pointer (&path);
          g_clear_pointer (&path, g_free);
        }
    }

  if (parser->gi_gir_path != NULL)
    {
      for (dir = (const char *const *) parser->gi_gir_path; *dir; dir++)
        {
          if (**dir == '\0')
            continue;

          path = g_build_filename (*dir, girname, NULL);
          g_debug ("Trying %s from GI_GIR_PATH", path);
          if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
            return g_steal_pointer (&path);
          g_clear_pointer (&path, g_free);
        }
    }

  path = g_build_filename (g_get_user_data_dir (), GIR_SUFFIX, girname, NULL);
  g_debug ("Trying %s from user data dir", path);
  if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
    return g_steal_pointer (&path);
  g_clear_pointer (&path, g_free);

  for (dir = datadirs; *dir; dir++)
    {
      path = g_build_filename (*dir, GIR_SUFFIX, girname, NULL);
      g_debug ("Trying %s from system data dirs", path);
      if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
        return g_steal_pointer (&path);
      g_clear_pointer (&path, g_free);
    }

  path = g_build_filename (GIR_DIR, girname, NULL);
  g_debug ("Trying %s from GIR_DIR", path);
  if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
    return g_steal_pointer (&path);
  g_clear_pointer (&path, g_free);

  path = g_build_filename (GOBJECT_INTROSPECTION_DATADIR, GIR_SUFFIX, girname, NULL);
  g_debug ("Trying %s from DATADIR", path);
  if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
    return g_steal_pointer (&path);
  g_clear_pointer (&path, g_free);

#ifdef G_OS_UNIX
  path = g_build_filename ("/usr/share", GIR_SUFFIX, girname, NULL);
  g_debug ("Trying %s", path);
  if (g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR))
    return g_steal_pointer (&path);
  g_clear_pointer (&path, g_free);
#endif

  g_debug ("Did not find %s", girname);
  return NULL;
}

#define MISSING_ATTRIBUTE(context,error,element,attribute)                                \
  do {                                                                          \
    int line_number, char_number;                                                \
    g_markup_parse_context_get_position (context, &line_number, &char_number);  \
    g_set_error (error,                                                         \
                    G_MARKUP_ERROR,                                                \
                 G_MARKUP_ERROR_INVALID_CONTENT,                                \
                 "Line %d, character %d: The attribute '%s' on the element '%s' must be specified",    \
                 line_number, char_number, attribute, element);                \
  } while (0)

static const char *
find_attribute (const char   *name,
                const char **attribute_names,
                const char **attribute_values)
{
  size_t i;

  for (i = 0; attribute_names[i] != NULL; i++)
    if (strcmp (attribute_names[i], name) == 0)
      return attribute_values[i];

  return 0;
}

static void
state_switch (ParseContext *ctx, ParseState newstate)
{
  g_assert (ctx->state != newstate);
  ctx->prev_state = ctx->state;
  ctx->state = newstate;

  if (ctx->state == STATE_PASSTHROUGH)
    ctx->unknown_depth = 1;
}

static GIIrNode *
pop_node (ParseContext *ctx)
{
  GSList *top;
  GIIrNode *node;
  g_assert (ctx->node_stack != 0);

  top = ctx->node_stack;
  node = top->data;

  g_debug ("popping node %d %s", node->type, node->name);
  ctx->node_stack = top->next;
  g_slist_free_1 (top);
  return node;
}

static void
push_node (ParseContext *ctx, GIIrNode *node)
{
  g_assert (node != NULL);
  g_debug ("pushing node %d %s", node->type, node->name);
  ctx->node_stack = g_slist_prepend (ctx->node_stack, node);
}

static GIIrNodeType * parse_type_internal (GIIrModule *module,
                                           const char *str,
                                           char **next,
                                           gboolean in_glib,
                                           gboolean in_gobject);

typedef struct {
  const char *str;
  size_t size;
  unsigned int is_signed : 1;
} IntegerAliasInfo;

static IntegerAliasInfo integer_aliases[] = {
  /* It is platform-dependent whether gchar is signed or unsigned, but
   * GObject-Introspection has traditionally treated it as signed,
   * so continue to hard-code that instead of using INTEGER_ALIAS */
  { "gchar", sizeof (gchar), 1 },

#define INTEGER_ALIAS(T) { #T, sizeof (T), G_SIGNEDNESS_OF (T) }
  INTEGER_ALIAS (guchar),
  INTEGER_ALIAS (gshort),
  INTEGER_ALIAS (gushort),
  INTEGER_ALIAS (gint),
  INTEGER_ALIAS (guint),
  INTEGER_ALIAS (glong),
  INTEGER_ALIAS (gulong),
  INTEGER_ALIAS (gssize),
  INTEGER_ALIAS (gsize),
  INTEGER_ALIAS (gintptr),
  INTEGER_ALIAS (guintptr),
  INTEGER_ALIAS (off_t),
  INTEGER_ALIAS (time_t),
#ifdef G_OS_UNIX
  INTEGER_ALIAS (dev_t),
  INTEGER_ALIAS (gid_t),
  INTEGER_ALIAS (pid_t),
  INTEGER_ALIAS (socklen_t),
  INTEGER_ALIAS (uid_t),
#endif
#undef INTEGER_ALIAS
};

typedef struct {
  const char *str;
  int tag;
  gboolean pointer;
} BasicTypeInfo;

#define BASIC_TYPE_FIXED_OFFSET 3

static BasicTypeInfo basic_types[] = {
    { "none",      GI_TYPE_TAG_VOID,    0 },
    { "gpointer",  GI_TYPE_TAG_VOID,    1 },

    { "gboolean",  GI_TYPE_TAG_BOOLEAN, 0 },
    { "gint8",     GI_TYPE_TAG_INT8,    0 }, /* Start of BASIC_TYPE_FIXED_OFFSET */
    { "guint8",    GI_TYPE_TAG_UINT8,   0 },
    { "gint16",    GI_TYPE_TAG_INT16,   0 },
    { "guint16",   GI_TYPE_TAG_UINT16,  0 },
    { "gint32",    GI_TYPE_TAG_INT32,   0 },
    { "guint32",   GI_TYPE_TAG_UINT32,  0 },
    { "gint64",    GI_TYPE_TAG_INT64,   0 },
    { "guint64",   GI_TYPE_TAG_UINT64,  0 },
    { "gfloat",    GI_TYPE_TAG_FLOAT,   0 },
    { "gdouble",   GI_TYPE_TAG_DOUBLE,  0 },
    { "GType",     GI_TYPE_TAG_GTYPE,   0 },
    { "utf8",      GI_TYPE_TAG_UTF8,    1 },
    { "filename",  GI_TYPE_TAG_FILENAME,1 },
    { "gunichar",  GI_TYPE_TAG_UNICHAR, 0 },
};

static const BasicTypeInfo *
parse_basic (const char *str)
{
  size_t i;
  size_t n_basic = G_N_ELEMENTS (basic_types);

  for (i = 0; i < n_basic; i++)
    {
      if (strcmp (str, basic_types[i].str) == 0)
        return &(basic_types[i]);
    }
  for (i = 0; i < G_N_ELEMENTS (integer_aliases); i++)
    {
      if (strcmp (str, integer_aliases[i].str) == 0)
        {
          switch (integer_aliases[i].size)
            {
            case sizeof (uint8_t):
              if (integer_aliases[i].is_signed)
                return &basic_types[BASIC_TYPE_FIXED_OFFSET];
              else
                return &basic_types[BASIC_TYPE_FIXED_OFFSET+1];
              break;
            case sizeof (uint16_t):
              if (integer_aliases[i].is_signed)
                return &basic_types[BASIC_TYPE_FIXED_OFFSET+2];
              else
                return &basic_types[BASIC_TYPE_FIXED_OFFSET+3];
              break;
            case sizeof (uint32_t):
              if (integer_aliases[i].is_signed)
                return &basic_types[BASIC_TYPE_FIXED_OFFSET+4];
              else
                return &basic_types[BASIC_TYPE_FIXED_OFFSET+5];
              break;
            case sizeof (uint64_t):
              if (integer_aliases[i].is_signed)
                return &basic_types[BASIC_TYPE_FIXED_OFFSET+6];
              else
                return &basic_types[BASIC_TYPE_FIXED_OFFSET+7];
              break;
            default:
              g_assert_not_reached ();
            }
        }
    }
  return NULL;
}

static GIIrNodeType *
parse_type_internal (GIIrModule   *module,
                     const char   *str,
                     char        **next,
                     gboolean      in_glib,
                     gboolean      in_gobject)
{
  const BasicTypeInfo *basic;
  GIIrNodeType *type;
  char *temporary_type = NULL;

  type = (GIIrNodeType *)gi_ir_node_new (GI_IR_NODE_TYPE, module);

  type->unparsed = g_strdup (str);

  /* See comment below on GLib.List handling */
  if (in_gobject && strcmp (str, "Type") == 0)
    {
      temporary_type = g_strdup ("GLib.Type");
      str = temporary_type;
    }

  basic = parse_basic (str);
  if (basic != NULL)
    {
      type->is_basic = TRUE;
      type->tag = basic->tag;
      type->is_pointer = basic->pointer;

      str += strlen(basic->str);
    }
  else if (in_glib)
    {
      /* If we're inside GLib, handle "List" etc. by prefixing with
       * "GLib." so the parsing code below doesn't have to get more
       * special.
       */
      if (g_str_has_prefix (str, "List<") ||
          strcmp (str, "List") == 0)
        {
          temporary_type = g_strdup_printf ("GLib.List%s", str + 4);
          str = temporary_type;
        }
      else if (g_str_has_prefix (str, "SList<") ||
          strcmp (str, "SList") == 0)
        {
          temporary_type = g_strdup_printf ("GLib.SList%s", str + 5);
          str = temporary_type;
        }
      else if (g_str_has_prefix (str, "HashTable<") ||
          strcmp (str, "HashTable") == 0)
        {
          temporary_type = g_strdup_printf ("GLib.HashTable%s", str + 9);
          str = temporary_type;
        }
      else if (g_str_has_prefix (str, "Error<") ||
          strcmp (str, "Error") == 0)
        {
          temporary_type = g_strdup_printf ("GLib.Error%s", str + 5);
          str = temporary_type;
        }
    }

  if (basic != NULL)
    /* found a basic type */;
  else if (g_str_has_prefix (str, "GLib.List") ||
           g_str_has_prefix (str, "GLib.SList"))
    {
      str += strlen ("GLib.");
      if (g_str_has_prefix (str, "List"))
        {
          type->tag = GI_TYPE_TAG_GLIST;
          type->is_glist = TRUE;
          type->is_pointer = TRUE;
          str += strlen ("List");
        }
      else
        {
          type->tag = GI_TYPE_TAG_GSLIST;
          type->is_gslist = TRUE;
          type->is_pointer = TRUE;
          str += strlen ("SList");
        }
    }
  else if (g_str_has_prefix (str, "GLib.HashTable"))
    {
      str += strlen ("GLib.");

      type->tag = GI_TYPE_TAG_GHASH;
      type->is_ghashtable = TRUE;
      type->is_pointer = TRUE;
      str += strlen ("HashTable");
    }
  else if (g_str_has_prefix (str, "GLib.Error"))
    {
      str += strlen ("GLib.");

      type->tag = GI_TYPE_TAG_ERROR;
      type->is_error = TRUE;
      type->is_pointer = TRUE;
      str += strlen ("Error");

      /* Silence a scan-build false positive */
      g_assert (str != NULL);

      if (*str == '<')
        {
          char *tmp, *end;
          (str)++;

          end = strchr (str, '>');
          tmp = g_strndup (str, end - str);
          type->errors = g_strsplit (tmp, ",", 0);
          g_free (tmp);

          str = end;
        }
    }
  else
    {
      const char *start;
      type->tag = GI_TYPE_TAG_INTERFACE;
      type->is_interface = TRUE;
      start = str;

      /* must be an interface type */
      while (g_ascii_isalnum (*str) ||
             *str == '.' ||
             *str == '-' ||
             *str == '_' ||
             *str == ':')
        (str)++;

      type->giinterface = g_strndup (start, str - start);
    }

  if (next)
    *next = (char*)str;
  g_assert (type->tag >= 0 && type->tag < GI_TYPE_TAG_N_TYPES);
  g_free (temporary_type);
  return type;

/* error: */
  gi_ir_node_free ((GIIrNode *)type);
  g_free (temporary_type);
  return NULL;
}

static const char *
resolve_aliases (ParseContext *ctx, const char *type)
{
  void *orig;
  void *value;
  GSList *seen_values = NULL;
  const char *lookup;
  char *prefixed;

  if (strchr (type, '.') == NULL)
    {
      prefixed = g_strdup_printf ("%s.%s", ctx->namespace, type);
      lookup = prefixed;
    }
  else
    {
      lookup = type;
      prefixed = NULL;
    }

  seen_values = g_slist_prepend (seen_values, (char*)lookup);
  while (g_hash_table_lookup_extended (ctx->current_module->aliases, lookup, &orig, &value))
    {
      g_debug ("Resolved: %s => %s", lookup, (char*)value);
      lookup = value;
      if (g_slist_find_custom (seen_values, lookup,
                               (GCompareFunc)strcmp) != NULL)
        break;
      seen_values = g_slist_prepend (seen_values, (char*) lookup);
    }
  g_slist_free (seen_values);

  if (lookup == prefixed)
    lookup = type;

  g_free (prefixed);

  return lookup;
}

static void
is_pointer_or_disguised_structure (ParseContext *ctx,
                                   const char *type,
                                   gboolean *is_pointer,
                                   gboolean *is_disguised)
{
  const char *lookup;
  char *prefixed;

  if (strchr (type, '.') == NULL)
    {
      prefixed = g_strdup_printf ("%s.%s", ctx->namespace, type);
      lookup = prefixed;
    }
  else
    {
      lookup = type;
      prefixed = NULL;
    }

  if (is_pointer != NULL)
    *is_pointer = g_hash_table_lookup (ctx->current_module->pointer_structures, lookup) != NULL;
  if (is_disguised != NULL)
    *is_disguised = g_hash_table_lookup (ctx->current_module->disguised_structures, lookup) != NULL;

  g_free (prefixed);
}

static GIIrNodeType *
parse_type (ParseContext *ctx, const char *type)
{
  GIIrNodeType *node;
  const BasicTypeInfo *basic;
  gboolean in_glib, in_gobject;

  in_glib = strcmp (ctx->namespace, "GLib") == 0;
  in_gobject = strcmp (ctx->namespace, "GObject") == 0;

  /* Do not search aliases for basic types */
  basic = parse_basic (type);
  if (basic == NULL)
    type = resolve_aliases (ctx, type);

  node = parse_type_internal (ctx->current_module, type, NULL, in_glib, in_gobject);
  if (node)
    g_debug ("Parsed type: %s => %d", type, node->tag);
  else
    g_critical ("Failed to parse type: '%s'", type);

  return node;
}

static gboolean
introspectable_prelude (GMarkupParseContext  *context,
                        const char          **attribute_names,
                        const char          **attribute_values,
                        ParseContext         *ctx,
                        ParseState            new_state)
{
  const char *introspectable_arg;
  const char *shadowed_by;
  gboolean introspectable;

  g_assert (ctx->state != STATE_PASSTHROUGH);

  introspectable_arg = find_attribute ("introspectable", attribute_names, attribute_values);
  shadowed_by = find_attribute ("shadowed-by", attribute_names, attribute_values);

  introspectable = !(introspectable_arg && atoi (introspectable_arg) == 0) && shadowed_by == NULL;

  if (introspectable)
    state_switch (ctx, new_state);
  else
    state_switch (ctx, STATE_PASSTHROUGH);

  return introspectable;
}

static gboolean
start_glib_boxed (GMarkupParseContext  *context,
                  const char           *element_name,
                  const char          **attribute_names,
                  const char          **attribute_values,
                  ParseContext         *ctx,
                  GError              **error)
{
  const char *name;
  const char *typename;
  const char *typeinit;
  const char *deprecated;
  GIIrNodeBoxed *boxed;

  if (!(strcmp (element_name, "glib:boxed") == 0 &&
        ctx->state == STATE_NAMESPACE))
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_BOXED))
    return TRUE;

  name = find_attribute ("glib:name", attribute_names, attribute_values);
  typename = find_attribute ("glib:type-name", attribute_names, attribute_values);
  typeinit = find_attribute ("glib:get-type", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:name");
      return FALSE;
    }
  else if (typename == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:type-name");
      return FALSE;
    }
  else if (typeinit == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:get-type");
      return FALSE;
    }

  boxed = (GIIrNodeBoxed *) gi_ir_node_new (GI_IR_NODE_BOXED,
                                            ctx->current_module);

  ((GIIrNode *)boxed)->name = g_strdup (name);
  boxed->gtype_name = g_strdup (typename);
  boxed->gtype_init = g_strdup (typeinit);
  if (deprecated)
    boxed->deprecated = TRUE;
  else
    boxed->deprecated = FALSE;

  push_node (ctx, (GIIrNode *)boxed);
  ctx->current_module->entries =
    g_list_append (ctx->current_module->entries, boxed);

  return TRUE;
}

static gboolean
start_function (GMarkupParseContext  *context,
                const char           *element_name,
                const char          **attribute_names,
                const char          **attribute_values,
                ParseContext         *ctx,
                GError              **error)
{
  const char *name;
  const char *shadows;
  const char *symbol;
  const char *deprecated;
  const char *throws;
  const char *set_property;
  const char *get_property;
  GIIrNodeFunction *function;
  gboolean found = FALSE;
  ParseState in_embedded_state = STATE_NONE;

  switch (ctx->state)
    {
    case STATE_NAMESPACE:
      found = (strcmp (element_name, "function") == 0 ||
               strcmp (element_name, "callback") == 0);
      break;
    case STATE_CLASS:
    case STATE_BOXED:
    case STATE_STRUCT:
    case STATE_UNION:
      found = strcmp (element_name, "constructor") == 0;
      /* fallthrough */
      G_GNUC_FALLTHROUGH;
    case STATE_INTERFACE:
      found = (found ||
               strcmp (element_name, "function") == 0 ||
               strcmp (element_name, "method") == 0 ||
               strcmp (element_name, "callback") == 0);
      break;
    case STATE_ENUM:
      found = strcmp (element_name, "function") == 0;
      break;
    case STATE_CLASS_FIELD:
    case STATE_STRUCT_FIELD:
      found = (found || strcmp (element_name, "callback") == 0);
      in_embedded_state = ctx->state;
      break;
    default:
      break;
    }

  if (!found)
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_FUNCTION))
    return TRUE;

  ctx->in_embedded_state = in_embedded_state;

  name = find_attribute ("name", attribute_names, attribute_values);
  shadows = find_attribute ("shadows", attribute_names, attribute_values);
  symbol = find_attribute ("c:identifier", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);
  throws = find_attribute ("throws", attribute_names, attribute_values);
  set_property = find_attribute ("glib:set-property", attribute_names, attribute_values);
  get_property = find_attribute ("glib:get-property", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }
  else if (strcmp (element_name, "callback") != 0 && symbol == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "c:identifier");
      return FALSE;
    }

  if (shadows)
    name = shadows;

  function = (GIIrNodeFunction *) gi_ir_node_new (GI_IR_NODE_FUNCTION,
                                                  ctx->current_module);

  ((GIIrNode *)function)->name = g_strdup (name);
  function->symbol = g_strdup (symbol);
  function->parameters = NULL;
  if (deprecated)
    function->deprecated = TRUE;
  else
    function->deprecated = FALSE;

  if (strcmp (element_name, "method") == 0 ||
      strcmp (element_name, "constructor") == 0)
    {
      function->is_method = TRUE;

      if (strcmp (element_name, "constructor") == 0)
        function->is_constructor = TRUE;
      else
        function->is_constructor = FALSE;

      if (set_property != NULL)
        {
          function->is_setter = TRUE;
          function->is_getter = FALSE;
          function->property = g_strdup (set_property);
        }
      else if (get_property != NULL)
        {
          function->is_setter = FALSE;
          function->is_getter = TRUE;
          function->property = g_strdup (get_property);
        }
      else
        {
          function->is_setter = FALSE;
          function->is_getter = FALSE;
          function->property = NULL;
        }
    }
  else
    {
      function->is_method = FALSE;
      function->is_setter = FALSE;
      function->is_getter = FALSE;
      function->is_constructor = FALSE;
      if (strcmp (element_name, "callback") == 0)
        ((GIIrNode *)function)->type = GI_IR_NODE_CALLBACK;
    }

  if (throws && strcmp (throws, "1") == 0)
    function->throws = TRUE;
  else
    function->throws = FALSE;

  if (ctx->node_stack == NULL)
    {
      ctx->current_module->entries =
        g_list_append (ctx->current_module->entries, function);
    }
  else if (ctx->current_typed)
    {
      GIIrNodeField *field;

      field = (GIIrNodeField *)ctx->current_typed;
      field->callback = function;
    }
  else
    switch (CURRENT_NODE (ctx)->type)
      {
      case GI_IR_NODE_INTERFACE:
      case GI_IR_NODE_OBJECT:
        {
          GIIrNodeInterface *iface;

          iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);
          iface->members = g_list_append (iface->members, function);
        }
        break;
      case GI_IR_NODE_BOXED:
        {
          GIIrNodeBoxed *boxed;

          boxed = (GIIrNodeBoxed *)CURRENT_NODE (ctx);
          boxed->members = g_list_append (boxed->members, function);
        }
        break;
      case GI_IR_NODE_STRUCT:
        {
          GIIrNodeStruct *struct_;

          struct_ = (GIIrNodeStruct *)CURRENT_NODE (ctx);
          struct_->members = g_list_append (struct_->members, function);                }
        break;
      case GI_IR_NODE_UNION:
        {
          GIIrNodeUnion *union_;

          union_ = (GIIrNodeUnion *)CURRENT_NODE (ctx);
          union_->members = g_list_append (union_->members, function);
        }
        break;
      case GI_IR_NODE_ENUM:
      case GI_IR_NODE_FLAGS:
        {
          GIIrNodeEnum *enum_;

          enum_ = (GIIrNodeEnum *)CURRENT_NODE (ctx);
          enum_->methods = g_list_append (enum_->methods, function);
        }
        break;
      default:
        g_assert_not_reached ();
      }

  push_node(ctx, (GIIrNode *)function);

  return TRUE;
}

static void
parse_property_transfer (GIIrNodeProperty *property,
                         const char       *transfer,
                         ParseContext     *ctx)
{
  if (transfer == NULL)
  {
#if 0
    GIIrNodeInterface *iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);

    g_debug ("required attribute 'transfer-ownership' is missing from "
             "property '%s' in type '%s.%s'. Assuming 'none'",
             property->node.name, ctx->namespace, iface->node.name);
#endif
    transfer = "none";
  }
  if (strcmp (transfer, "none") == 0)
    {
      property->transfer = FALSE;
      property->shallow_transfer = FALSE;
    }
  else if (strcmp (transfer, "container") == 0)
    {
      property->transfer = FALSE;
      property->shallow_transfer = TRUE;
    }
  else if (strcmp (transfer, "full") == 0)
    {
      property->transfer = TRUE;
      property->shallow_transfer = FALSE;
    }
  else
    {
      GIIrNodeInterface *iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);

      g_warning ("Unknown transfer-ownership value: '%s' for property '%s' in "
                 "type '%s.%s'", transfer, property->node.name, ctx->namespace,
                 iface->node.name);
    }
}

static gboolean
parse_param_transfer (GIIrNodeParam *param, const char *transfer, const char *name,
                      GError **error)
{
  if (transfer == NULL)
  {
    g_set_error (error, G_MARKUP_ERROR,
                 G_MARKUP_ERROR_INVALID_CONTENT,
                 "required attribute 'transfer-ownership' missing");
    return FALSE;
  }
  else if (strcmp (transfer, "none") == 0)
    {
      param->transfer = FALSE;
      param->shallow_transfer = FALSE;
    }
  else if (strcmp (transfer, "container") == 0)
    {
      param->transfer = FALSE;
      param->shallow_transfer = TRUE;
    }
  else if (strcmp (transfer, "full") == 0)
    {
      param->transfer = TRUE;
      param->shallow_transfer = FALSE;
    }
  else
    {
      g_set_error (error, G_MARKUP_ERROR,
                   G_MARKUP_ERROR_INVALID_CONTENT,
                   "invalid value for 'transfer-ownership': %s", transfer);
      return FALSE;
    }
  return TRUE;
}

static gboolean
start_instance_parameter (GMarkupParseContext  *context,
                          const char           *element_name,
                          const char          **attribute_names,
                          const char          **attribute_values,
                          ParseContext         *ctx,
                          GError              **error)
{
  const char *transfer;
  gboolean transfer_full;

  if (!(strcmp (element_name, "instance-parameter") == 0 &&
        ctx->state == STATE_FUNCTION_PARAMETERS))
    return FALSE;

  transfer = find_attribute ("transfer-ownership", attribute_names, attribute_values);

  state_switch (ctx, STATE_PASSTHROUGH);

  if (g_strcmp0 (transfer, "full") == 0)
    transfer_full = TRUE;
  else if (g_strcmp0 (transfer, "none") == 0)
    transfer_full = FALSE;
  else
    {
      g_set_error (error, G_MARKUP_ERROR,
                   G_MARKUP_ERROR_INVALID_CONTENT,
                   "invalid value for 'transfer-ownership' for instance parameter: %s", transfer);
      return FALSE;
    }

  switch (CURRENT_NODE (ctx)->type)
    {
    case GI_IR_NODE_FUNCTION:
    case GI_IR_NODE_CALLBACK:
      {
        GIIrNodeFunction *func;

        func = (GIIrNodeFunction *)CURRENT_NODE (ctx);
        func->instance_transfer_full = transfer_full;
      }
      break;
    case GI_IR_NODE_SIGNAL:
      {
        GIIrNodeSignal *signal;

        signal = (GIIrNodeSignal *)CURRENT_NODE (ctx);
        signal->instance_transfer_full = transfer_full;
      }
      break;
    case GI_IR_NODE_VFUNC:
      {
        GIIrNodeVFunc *vfunc;

        vfunc = (GIIrNodeVFunc *)CURRENT_NODE (ctx);
        vfunc->instance_transfer_full = transfer_full;
      }
      break;
    default:
      g_assert_not_reached ();
    }

  return TRUE;
}

static gboolean
start_parameter (GMarkupParseContext  *context,
                 const char           *element_name,
                 const char          **attribute_names,
                 const char          **attribute_values,
                 ParseContext         *ctx,
                 GError              **error)
{
  const char *name;
  const char *direction;
  const char *retval;
  const char *optional;
  const char *caller_allocates;
  const char *allow_none;
  const char *transfer;
  const char *scope;
  const char *closure;
  const char *destroy;
  const char *skip;
  const char *nullable;
  GIIrNodeParam *param;

  if (!(strcmp (element_name, "parameter") == 0 &&
        ctx->state == STATE_FUNCTION_PARAMETERS))
    return FALSE;

  name = find_attribute ("name", attribute_names, attribute_values);
  direction = find_attribute ("direction", attribute_names, attribute_values);
  retval = find_attribute ("retval", attribute_names, attribute_values);
  optional = find_attribute ("optional", attribute_names, attribute_values);
  allow_none = find_attribute ("allow-none", attribute_names, attribute_values);
  caller_allocates = find_attribute ("caller-allocates", attribute_names, attribute_values);
  transfer = find_attribute ("transfer-ownership", attribute_names, attribute_values);
  scope = find_attribute ("scope", attribute_names, attribute_values);
  closure = find_attribute ("closure", attribute_names, attribute_values);
  destroy = find_attribute ("destroy", attribute_names, attribute_values);
  skip = find_attribute ("skip", attribute_names, attribute_values);
  nullable = find_attribute ("nullable", attribute_names, attribute_values);

  if (name == NULL)
    name = "unknown";

  param = (GIIrNodeParam *)gi_ir_node_new (GI_IR_NODE_PARAM,
                                           ctx->current_module);

  ctx->current_typed = (GIIrNode*) param;
  ctx->current_typed->name = g_strdup (name);

  state_switch (ctx, STATE_FUNCTION_PARAMETER);

  if (direction && strcmp (direction, "out") == 0)
    {
      param->in = FALSE;
      param->out = TRUE;
      if (caller_allocates == NULL)
        param->caller_allocates = FALSE;
      else
        param->caller_allocates = strcmp (caller_allocates, "1") == 0;
    }
  else if (direction && strcmp (direction, "inout") == 0)
    {
      param->in = TRUE;
      param->out = TRUE;
      param->caller_allocates = FALSE;
    }
  else
    {
      param->in = TRUE;
      param->out = FALSE;
      param->caller_allocates = FALSE;
    }

  if (retval && strcmp (retval, "1") == 0)
    param->retval = TRUE;
  else
    param->retval = FALSE;

  if (optional && strcmp (optional, "1") == 0)
    param->optional = TRUE;
  else
    param->optional = FALSE;

  if (nullable && strcmp (nullable, "1") == 0)
    param->nullable = TRUE;
  else
    param->nullable = FALSE;

  if (allow_none && strcmp (allow_none, "1") == 0)
    {
      if (param->out)
        param->optional = TRUE;
      else
        param->nullable = TRUE;
    }

  if (skip && strcmp (skip, "1") == 0)
    param->skip = TRUE;
  else
    param->skip = FALSE;

  if (!parse_param_transfer (param, transfer, name, error))
    return FALSE;

  if (scope && strcmp (scope, "call") == 0)
    param->scope = GI_SCOPE_TYPE_CALL;
  else if (scope && strcmp (scope, "async") == 0)
    param->scope = GI_SCOPE_TYPE_ASYNC;
  else if (scope && strcmp (scope, "notified") == 0)
    param->scope = GI_SCOPE_TYPE_NOTIFIED;
  else if (scope && strcmp (scope, "forever") == 0)
    param->scope = GI_SCOPE_TYPE_FOREVER;
  else
    param->scope = GI_SCOPE_TYPE_INVALID;

  param->closure = closure ? atoi (closure) : -1;
  param->destroy = destroy ? atoi (destroy) : -1;

  switch (CURRENT_NODE (ctx)->type)
    {
    case GI_IR_NODE_FUNCTION:
    case GI_IR_NODE_CALLBACK:
      {
        GIIrNodeFunction *func;

        func = (GIIrNodeFunction *)CURRENT_NODE (ctx);
        func->parameters = g_list_append (func->parameters, param);
      }
      break;
    case GI_IR_NODE_SIGNAL:
      {
        GIIrNodeSignal *signal;

        signal = (GIIrNodeSignal *)CURRENT_NODE (ctx);
        signal->parameters = g_list_append (signal->parameters, param);
      }
      break;
    case GI_IR_NODE_VFUNC:
      {
        GIIrNodeVFunc *vfunc;

        vfunc = (GIIrNodeVFunc *)CURRENT_NODE (ctx);
        vfunc->parameters = g_list_append (vfunc->parameters, param);
      }
      break;
    default:
      g_assert_not_reached ();
    }

  return TRUE;
}

static gboolean
start_field (GMarkupParseContext  *context,
             const char           *element_name,
             const char          **attribute_names,
             const char          **attribute_values,
             ParseContext         *ctx,
             GError              **error)
{
  const char *name;
  const char *readable;
  const char *writable;
  const char *bits;
  const char *branch;
  GIIrNodeField *field;
  ParseState target_state;
  gboolean introspectable;
  guint64 parsed_bits;

  switch (ctx->state)
    {
    case STATE_CLASS:
      target_state = STATE_CLASS_FIELD;
      break;
    case STATE_BOXED:
      target_state = STATE_BOXED_FIELD;
      break;
    case STATE_STRUCT:
      target_state = STATE_STRUCT_FIELD;
      break;
    case STATE_UNION:
      target_state = STATE_UNION_FIELD;
      break;
    case STATE_INTERFACE:
      target_state = STATE_INTERFACE_FIELD;
      break;
    default:
      return FALSE;
    }

  if (strcmp (element_name, "field") != 0)
    return FALSE;

  g_assert (ctx->state != STATE_PASSTHROUGH);

  /* We handle introspectability specially here; we replace with just gpointer
   * for the type.
   */
  introspectable = introspectable_prelude (context, attribute_names, attribute_values, ctx, target_state);

  name = find_attribute ("name", attribute_names, attribute_values);
  readable = find_attribute ("readable", attribute_names, attribute_values);
  writable = find_attribute ("writable", attribute_names, attribute_values);
  bits = find_attribute ("bits", attribute_names, attribute_values);
  branch = find_attribute ("branch", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }

  field = (GIIrNodeField *)gi_ir_node_new (GI_IR_NODE_FIELD,
                                           ctx->current_module);
  if (introspectable)
    {
      ctx->current_typed = (GIIrNode*) field;
    }
  else
    {
      field->type = parse_type (ctx, "gpointer");
    }

  ((GIIrNode *)field)->name = g_strdup (name);
  /* Fields are assumed to be read-only.
   * (see also girwriter.py and generate.c)
   */
  field->readable = readable == NULL || strcmp (readable, "0") == 0;
  field->writable = writable != NULL && strcmp (writable, "1") == 0;

  if (bits == NULL)
    field->bits = 0;
  else if (g_ascii_string_to_unsigned (bits, 10, 0, G_MAXUINT, &parsed_bits, error))
    field->bits = parsed_bits;
  else
    {
      gi_ir_node_free ((GIIrNode *) field);
      return FALSE;
    }

  switch (CURRENT_NODE (ctx)->type)
    {
    case GI_IR_NODE_OBJECT:
      {
        GIIrNodeInterface *iface;

        iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);
        iface->members = g_list_append (iface->members, field);
      }
      break;
    case GI_IR_NODE_INTERFACE:
      {
        GIIrNodeInterface *iface;

        iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);
        iface->members = g_list_append (iface->members, field);
      }
      break;
    case GI_IR_NODE_BOXED:
      {
        GIIrNodeBoxed *boxed;

        boxed = (GIIrNodeBoxed *)CURRENT_NODE (ctx);
                boxed->members = g_list_append (boxed->members, field);
      }
      break;
    case GI_IR_NODE_STRUCT:
      {
        GIIrNodeStruct *struct_;

        struct_ = (GIIrNodeStruct *)CURRENT_NODE (ctx);
        struct_->members = g_list_append (struct_->members, field);
      }
      break;
    case GI_IR_NODE_UNION:
      {
        GIIrNodeUnion *union_;

        union_ = (GIIrNodeUnion *)CURRENT_NODE (ctx);
        union_->members = g_list_append (union_->members, field);
        if (branch)
          {
            GIIrNodeConstant *constant;

            constant = (GIIrNodeConstant *) gi_ir_node_new (GI_IR_NODE_CONSTANT,
                                                            ctx->current_module);
            ((GIIrNode *)constant)->name = g_strdup (name);
            constant->value = g_strdup (branch);
            constant->type = union_->discriminator_type;
            constant->deprecated = FALSE;

            union_->discriminators = g_list_append (union_->discriminators, constant);
          }
      }
      break;
    default:
      g_assert_not_reached ();
    }

  return TRUE;
}

static gboolean
start_alias (GMarkupParseContext  *context,
             const char           *element_name,
             const char          **attribute_names,
             const char          **attribute_values,
             ParseContext         *ctx,
             GError              **error)
{
  const char *name;

  name = find_attribute ("name", attribute_names, attribute_values);
  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }

  ctx->current_alias = g_strdup (name);
  state_switch (ctx, STATE_ALIAS);

  return TRUE;
}

static gboolean
start_enum (GMarkupParseContext  *context,
            const char           *element_name,
            const char          **attribute_names,
            const char          **attribute_values,
            ParseContext         *ctx,
            GError              **error)
{
  const char *name;
  const char *typename;
  const char *typeinit;
  const char *deprecated;
  const char *error_domain;
  GIIrNodeEnum *enum_;

  if (!((strcmp (element_name, "enumeration") == 0 && ctx->state == STATE_NAMESPACE) ||
        (strcmp (element_name, "bitfield") == 0 && ctx->state == STATE_NAMESPACE)))
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_ENUM))
    return TRUE;

  name = find_attribute ("name", attribute_names, attribute_values);
  typename = find_attribute ("glib:type-name", attribute_names, attribute_values);
  typeinit = find_attribute ("glib:get-type", attribute_names, attribute_values);
  error_domain = find_attribute ("glib:error-domain", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }

  if (strcmp (element_name, "enumeration") == 0)
    enum_ = (GIIrNodeEnum *) gi_ir_node_new (GI_IR_NODE_ENUM,
                                             ctx->current_module);
  else
    enum_ = (GIIrNodeEnum *) gi_ir_node_new (GI_IR_NODE_FLAGS,
                                             ctx->current_module);
  ((GIIrNode *)enum_)->name = g_strdup (name);
  enum_->gtype_name = g_strdup (typename);
  enum_->gtype_init = g_strdup (typeinit);
  enum_->error_domain = g_strdup (error_domain);

  if (deprecated)
    enum_->deprecated = TRUE;
  else
    enum_->deprecated = FALSE;

  push_node (ctx, (GIIrNode *) enum_);
  ctx->current_module->entries =
    g_list_append (ctx->current_module->entries, enum_);

  return TRUE;
}

static gboolean
start_property (GMarkupParseContext  *context,
                const char           *element_name,
                const char          **attribute_names,
                const char          **attribute_values,
                ParseContext         *ctx,
                GError              **error)
{
  ParseState target_state;
  const char *name;
  const char *readable;
  const char *writable;
  const char *construct;
  const char *construct_only;
  const char *transfer;
  const char *setter;
  const char *getter;
  GIIrNodeProperty *property;
  GIIrNodeInterface *iface;

  if (!(strcmp (element_name, "property") == 0 &&
        (ctx->state == STATE_CLASS ||
         ctx->state == STATE_INTERFACE)))
    return FALSE;

  if (ctx->state == STATE_CLASS)
    target_state = STATE_CLASS_PROPERTY;
  else if (ctx->state == STATE_INTERFACE)
    target_state = STATE_INTERFACE_PROPERTY;
  else
    g_assert_not_reached ();

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, target_state))
    return TRUE;


  name = find_attribute ("name", attribute_names, attribute_values);
  readable = find_attribute ("readable", attribute_names, attribute_values);
  writable = find_attribute ("writable", attribute_names, attribute_values);
  construct = find_attribute ("construct", attribute_names, attribute_values);
  construct_only = find_attribute ("construct-only", attribute_names, attribute_values);
  transfer = find_attribute ("transfer-ownership", attribute_names, attribute_values);
  setter = find_attribute ("setter", attribute_names, attribute_values);
  getter = find_attribute ("getter", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }

  property = (GIIrNodeProperty *) gi_ir_node_new (GI_IR_NODE_PROPERTY,
                                                  ctx->current_module);
  ctx->current_typed = (GIIrNode*) property;

  ((GIIrNode *)property)->name = g_strdup (name);

  /* Assume properties are readable */
  if (readable == NULL || strcmp (readable, "1") == 0)
    property->readable = TRUE;
  else
    property->readable = FALSE;
  if (writable && strcmp (writable, "1") == 0)
    property->writable = TRUE;
  else
    property->writable = FALSE;
  if (construct && strcmp (construct, "1") == 0)
    property->construct = TRUE;
  else
    property->construct = FALSE;
  if (construct_only && strcmp (construct_only, "1") == 0)
    property->construct_only = TRUE;
  else
    property->construct_only = FALSE;

  property->setter = g_strdup (setter);
  property->getter = g_strdup (getter);

  parse_property_transfer (property, transfer, ctx);

  iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);
  iface->members = g_list_append (iface->members, property);

  return TRUE;
}

static int64_t
parse_value (const char *str)
{
  char *shift_op;

  /* FIXME just a quick hack */
  shift_op = strstr (str, "<<");

  if (shift_op)
    {
      int64_t base, shift;

      base = g_ascii_strtoll (str, NULL, 10);
      shift = g_ascii_strtoll (shift_op + 3, NULL, 10);

      return base << shift;
    }
  else
    return g_ascii_strtoll (str, NULL, 10);

  return 0;
}

static gboolean
start_member (GMarkupParseContext  *context,
              const char           *element_name,
              const char          **attribute_names,
              const char          **attribute_values,
              ParseContext         *ctx,
              GError              **error)
{
  const char *name;
  const char *value;
  const char *deprecated;
  const char *c_identifier;
  GIIrNodeEnum *enum_;
  GIIrNodeValue *value_;

  if (!(strcmp (element_name, "member") == 0 &&
        ctx->state == STATE_ENUM))
    return FALSE;

  name = find_attribute ("name", attribute_names, attribute_values);
  value = find_attribute ("value", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);
  c_identifier = find_attribute ("c:identifier", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }

  value_ = (GIIrNodeValue *) gi_ir_node_new (GI_IR_NODE_VALUE,
                                             ctx->current_module);

  ((GIIrNode *)value_)->name = g_strdup (name);

  value_->value = parse_value (value);

  if (deprecated)
    value_->deprecated = TRUE;
  else
    value_->deprecated = FALSE;

  g_hash_table_insert (((GIIrNode *)value_)->attributes,
                       g_strdup ("c:identifier"),
                       g_strdup (c_identifier));

  enum_ = (GIIrNodeEnum *)CURRENT_NODE (ctx);
  enum_->values = g_list_append (enum_->values, value_);

  return TRUE;
}

static gboolean
start_constant (GMarkupParseContext  *context,
                const char           *element_name,
                const char          **attribute_names,
                const char          **attribute_values,
                ParseContext         *ctx,
                GError              **error)
{
  ParseState prev_state;
  ParseState target_state;
  const char *name;
  const char *value;
  const char *deprecated;
  GIIrNodeConstant *constant;

  if (!(strcmp (element_name, "constant") == 0 &&
        (ctx->state == STATE_NAMESPACE ||
         ctx->state == STATE_CLASS ||
         ctx->state == STATE_INTERFACE)))
    return FALSE;

  switch (ctx->state)
    {
    case STATE_NAMESPACE:
      target_state = STATE_NAMESPACE_CONSTANT;
      break;
    case STATE_CLASS:
      target_state = STATE_CLASS_CONSTANT;
      break;
    case STATE_INTERFACE:
      target_state = STATE_INTERFACE_CONSTANT;
      break;
    default:
      g_assert_not_reached ();
    }

  prev_state = ctx->state;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, target_state))
    return TRUE;

  name = find_attribute ("name", attribute_names, attribute_values);
  value = find_attribute ("value", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }
  else if (value == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "value");
      return FALSE;
    }

  constant = (GIIrNodeConstant *) gi_ir_node_new (GI_IR_NODE_CONSTANT,
                                                  ctx->current_module);

  ((GIIrNode *)constant)->name = g_strdup (name);
  constant->value = g_strdup (value);

  ctx->current_typed = (GIIrNode*) constant;

  if (deprecated)
    constant->deprecated = TRUE;
  else
    constant->deprecated = FALSE;

  if (prev_state == STATE_NAMESPACE)
    {
      push_node (ctx, (GIIrNode *) constant);
      ctx->current_module->entries =
        g_list_append (ctx->current_module->entries, constant);
    }
  else
    {
      GIIrNodeInterface *iface;

      iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);
      iface->members = g_list_append (iface->members, constant);
    }

  return TRUE;
}

static gboolean
start_interface (GMarkupParseContext  *context,
                 const char           *element_name,
                 const char          **attribute_names,
                 const char          **attribute_values,
                 ParseContext         *ctx,
                 GError              **error)
{
  const char *name;
  const char *typename;
  const char *typeinit;
  const char *deprecated;
  const char *glib_type_struct;
  GIIrNodeInterface *iface;

  if (!(strcmp (element_name, "interface") == 0 &&
        ctx->state == STATE_NAMESPACE))
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_INTERFACE))
    return TRUE;

  name = find_attribute ("name", attribute_names, attribute_values);
  typename = find_attribute ("glib:type-name", attribute_names, attribute_values);
  typeinit = find_attribute ("glib:get-type", attribute_names, attribute_values);
  glib_type_struct = find_attribute ("glib:type-struct", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }
  else if (typename == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:type-name");
      return FALSE;
    }
  else if (typeinit == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:get-type");
      return FALSE;
    }

  iface = (GIIrNodeInterface *) gi_ir_node_new (GI_IR_NODE_INTERFACE,
                                                ctx->current_module);
  ((GIIrNode *)iface)->name = g_strdup (name);
  iface->gtype_name = g_strdup (typename);
  iface->gtype_init = g_strdup (typeinit);
  iface->glib_type_struct = g_strdup (glib_type_struct);
  if (deprecated)
    iface->deprecated = TRUE;
  else
    iface->deprecated = FALSE;

  push_node (ctx, (GIIrNode *) iface);
  ctx->current_module->entries =
    g_list_append (ctx->current_module->entries, iface);

  return TRUE;
}

static gboolean
start_class (GMarkupParseContext  *context,
             const char           *element_name,
             const char          **attribute_names,
             const char          **attribute_values,
             ParseContext         *ctx,
             GError              **error)
{
  const char *name;
  const char *parent;
  const char *glib_type_struct;
  const char *typename;
  const char *typeinit;
  const char *deprecated;
  const char *abstract;
  const char *fundamental;
  const char *final;
  const char *ref_func;
  const char *unref_func;
  const char *set_value_func;
  const char *get_value_func;
  GIIrNodeInterface *iface;

  if (!(strcmp (element_name, "class") == 0 &&
        ctx->state == STATE_NAMESPACE))
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_CLASS))
    return TRUE;

  name = find_attribute ("name", attribute_names, attribute_values);
  parent = find_attribute ("parent", attribute_names, attribute_values);
  glib_type_struct = find_attribute ("glib:type-struct", attribute_names, attribute_values);
  typename = find_attribute ("glib:type-name", attribute_names, attribute_values);
  typeinit = find_attribute ("glib:get-type", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);
  abstract = find_attribute ("abstract", attribute_names, attribute_values);
  final = find_attribute ("final", attribute_names, attribute_values);
  fundamental = find_attribute ("glib:fundamental", attribute_names, attribute_values);
  ref_func = find_attribute ("glib:ref-func", attribute_names, attribute_values);
  unref_func = find_attribute ("glib:unref-func", attribute_names, attribute_values);
  set_value_func = find_attribute ("glib:set-value-func", attribute_names, attribute_values);
  get_value_func = find_attribute ("glib:get-value-func", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }
  else if (typename == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:type-name");
      return FALSE;
    }
  else if (typeinit == NULL && strcmp (typename, "GObject"))
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:get-type");
      return FALSE;
    }

  iface = (GIIrNodeInterface *) gi_ir_node_new (GI_IR_NODE_OBJECT,
                                                ctx->current_module);
  ((GIIrNode *)iface)->name = g_strdup (name);
  iface->gtype_name = g_strdup (typename);
  iface->gtype_init = g_strdup (typeinit);
  iface->parent = g_strdup (parent);
  iface->glib_type_struct = g_strdup (glib_type_struct);
  if (deprecated)
    iface->deprecated = TRUE;
  else
    iface->deprecated = FALSE;

  iface->abstract = abstract && strcmp (abstract, "1") == 0;
  iface->final_ = final && strcmp (final, "1") == 0;

  if (fundamental)
    iface->fundamental = TRUE;
  if (ref_func)
    iface->ref_func = g_strdup (ref_func);
  if (unref_func)
    iface->unref_func = g_strdup (unref_func);
  if (set_value_func)
    iface->set_value_func = g_strdup (set_value_func);
  if (get_value_func)
    iface->get_value_func = g_strdup (get_value_func);

  push_node (ctx, (GIIrNode *) iface);
  ctx->current_module->entries =
    g_list_append (ctx->current_module->entries, iface);

  return TRUE;
}

static gboolean
start_type (GMarkupParseContext  *context,
            const char           *element_name,
            const char          **attribute_names,
            const char          **attribute_values,
            ParseContext        *ctx,
            GError              **error)
{
  const char *name;
  const char *ctype;
  gboolean in_alias = FALSE;
  gboolean is_array;
  gboolean is_varargs;
  GIIrNodeType *typenode;

  is_array = strcmp (element_name, "array") == 0;
  is_varargs = strcmp (element_name, "varargs") == 0;

  if (!(is_array || is_varargs || (strcmp (element_name, "type") == 0)))
    return FALSE;

  if (ctx->state == STATE_TYPE)
    {
      ctx->type_depth++;
      ctx->type_stack = g_list_prepend (ctx->type_stack, ctx->type_parameters);
      ctx->type_parameters = NULL;
    }
  else if (ctx->state == STATE_FUNCTION_PARAMETER ||
           ctx->state == STATE_FUNCTION_RETURN ||
           ctx->state == STATE_STRUCT_FIELD ||
           ctx->state == STATE_UNION_FIELD ||
           ctx->state == STATE_CLASS_PROPERTY ||
           ctx->state == STATE_CLASS_FIELD ||
           ctx->state == STATE_INTERFACE_FIELD ||
           ctx->state == STATE_INTERFACE_PROPERTY ||
           ctx->state == STATE_BOXED_FIELD ||
           ctx->state == STATE_NAMESPACE_CONSTANT ||
           ctx->state == STATE_CLASS_CONSTANT ||
           ctx->state == STATE_INTERFACE_CONSTANT ||
           ctx->state == STATE_ALIAS
           )
    {
      if (ctx->state == STATE_ALIAS)
        in_alias = TRUE;
      state_switch (ctx, STATE_TYPE);
      ctx->type_depth = 1;
      ctx->type_stack = NULL;
      ctx->type_parameters = NULL;
    }

  name = find_attribute ("name", attribute_names, attribute_values);

  if (in_alias && ctx->current_alias)
    {
      char *key;
      char *value;

      if (name == NULL)
        {
          MISSING_ATTRIBUTE (context, error, element_name, "name");
          return FALSE;
        }

      key = g_strdup_printf ("%s.%s", ctx->namespace, ctx->current_alias);
      if (!strchr (name, '.'))
        {
          const BasicTypeInfo *basic = parse_basic (name);
          if (!basic)
            {
              /* For non-basic types, re-qualify the interface */
              value = g_strdup_printf ("%s.%s", ctx->namespace, name);
            }
          else
            {
              value = g_strdup (name);
            }
        }
      else
        value = g_strdup (name);

      g_hash_table_replace (ctx->aliases, key, value);

      return TRUE;
    }
  else if (!ctx->current_module || in_alias)
    return TRUE;

  if (!ctx->current_typed)
    {
      g_set_error (error,
                   G_MARKUP_ERROR,
                   G_MARKUP_ERROR_INVALID_CONTENT,
                   "The element <type> is invalid here");
      return FALSE;
    }

  if (is_varargs)
    return TRUE;

  if (is_array)
    {
      const char *zero;
      const char *len;
      const char *size;

      typenode = (GIIrNodeType *)gi_ir_node_new (GI_IR_NODE_TYPE,
                                                 ctx->current_module);

      typenode->tag = GI_TYPE_TAG_ARRAY;
      typenode->is_pointer = TRUE;
      typenode->is_array = TRUE;

      if (name && strcmp (name, "GLib.Array") == 0) {
        typenode->array_type = GI_ARRAY_TYPE_ARRAY;
      } else if (name && strcmp (name, "GLib.ByteArray") == 0) {
        typenode->array_type = GI_ARRAY_TYPE_BYTE_ARRAY;
      } else if (name && strcmp (name, "GLib.PtrArray") == 0) {
        typenode->array_type = GI_ARRAY_TYPE_PTR_ARRAY;
      } else {
        typenode->array_type = GI_ARRAY_TYPE_C;
      }

      if (typenode->array_type == GI_ARRAY_TYPE_C) {
          guint64 parsed_uint;

          zero = find_attribute ("zero-terminated", attribute_names, attribute_values);
          len = find_attribute ("length", attribute_names, attribute_values);
          size = find_attribute ("fixed-size", attribute_names, attribute_values);

          typenode->has_length = len != NULL;
          if (!typenode->has_length)
            typenode->length = -1;
          else if (g_ascii_string_to_unsigned (len, 10, 0, G_MAXUINT, &parsed_uint, error))
            typenode->length = parsed_uint;
          else
            {
              gi_ir_node_free ((GIIrNode *) typenode);
              return FALSE;
            }

          typenode->has_size = size != NULL;
          if (!typenode->has_size)
            typenode->size = -1;
          else if (g_ascii_string_to_unsigned (size, 10, 0, G_MAXSIZE, &parsed_uint, error))
            typenode->size = parsed_uint;
          else
            {
              gi_ir_node_free ((GIIrNode *) typenode);
              return FALSE;
            }

          if (zero)
            typenode->zero_terminated = strcmp(zero, "1") == 0;
          else
            /* If neither zero-terminated nor length nor fixed-size is given, assume zero-terminated. */
            typenode->zero_terminated = !(typenode->has_length || typenode->has_size);

          if (typenode->has_size && ctx->current_typed->type == GI_IR_NODE_FIELD)
            typenode->is_pointer = FALSE;
        } else {
          typenode->zero_terminated = FALSE;
          typenode->has_length = FALSE;
          typenode->length = -1;
          typenode->has_size = FALSE;
          typenode->size = -1;
        }
    }
  else
    {
      int pointer_depth;

      if (name == NULL)
        {
          MISSING_ATTRIBUTE (context, error, element_name, "name");
          return FALSE;
        }

      pointer_depth = 0;
      ctype = find_attribute ("c:type", attribute_names, attribute_values);
      if (ctype != NULL)
        {
          const char *cp = ctype + strlen(ctype) - 1;
          while (cp > ctype && *cp-- == '*')
            pointer_depth++;

          if (g_str_has_prefix (ctype, "gpointer")
              || g_str_has_prefix (ctype, "gconstpointer"))
            pointer_depth++;
        }

      if (ctx->current_typed->type == GI_IR_NODE_PARAM &&
          ((GIIrNodeParam *)ctx->current_typed)->out &&
          pointer_depth > 0)
        pointer_depth--;

      typenode = parse_type (ctx, name);

      /* A "pointer" structure is one where the c:type is a typedef that
       * to a pointer to a structure; we used to call them "disguised"
       * structures as well.
       */
      if (typenode->tag == GI_TYPE_TAG_INTERFACE)
        {
          gboolean is_pointer = FALSE;
          gboolean is_disguised = FALSE;

          is_pointer_or_disguised_structure (ctx, typenode->giinterface,
                                             &is_pointer,
                                             &is_disguised);

          if (is_pointer || is_disguised)
            pointer_depth++;
        }

      if (pointer_depth > 0)
        typenode->is_pointer = TRUE;
    }

  ctx->type_parameters = g_list_append (ctx->type_parameters, typenode);

  return TRUE;
}

static void
end_type_top (ParseContext *ctx)
{
  GIIrNodeType *typenode;

  if (!ctx->type_parameters)
    goto out;

  typenode = (GIIrNodeType*)ctx->type_parameters->data;

  /* Default to pointer for unspecified containers */
  if (typenode->tag == GI_TYPE_TAG_ARRAY ||
      typenode->tag == GI_TYPE_TAG_GLIST ||
      typenode->tag == GI_TYPE_TAG_GSLIST)
    {
      if (typenode->parameter_type1 == NULL)
        typenode->parameter_type1 = parse_type (ctx, "gpointer");
    }
  else if (typenode->tag == GI_TYPE_TAG_GHASH)
    {
      if (typenode->parameter_type1 == NULL)
        {
          typenode->parameter_type1 = parse_type (ctx, "gpointer");
          typenode->parameter_type2 = parse_type (ctx, "gpointer");
        }
    }

  switch (ctx->current_typed->type)
    {
    case GI_IR_NODE_PARAM:
      {
        GIIrNodeParam *param = (GIIrNodeParam *)ctx->current_typed;
        param->type = typenode;
      }
      break;
    case GI_IR_NODE_FIELD:
      {
        GIIrNodeField *field = (GIIrNodeField *)ctx->current_typed;
        field->type = typenode;
      }
      break;
    case GI_IR_NODE_PROPERTY:
      {
        GIIrNodeProperty *property = (GIIrNodeProperty *) ctx->current_typed;
        property->type = typenode;
      }
      break;
    case GI_IR_NODE_CONSTANT:
      {
        GIIrNodeConstant *constant = (GIIrNodeConstant *)ctx->current_typed;
        constant->type = typenode;
      }
      break;
    default:
      g_printerr("current node is %d\n", CURRENT_NODE (ctx)->type);
      g_assert_not_reached ();
    }
  g_list_free (ctx->type_parameters);

 out:
  ctx->type_depth = 0;
  ctx->type_parameters = NULL;
  ctx->current_typed = NULL;
}

static void
end_type_recurse (ParseContext *ctx)
{
  GIIrNodeType *parent;
  GIIrNodeType *param = NULL;

  parent = (GIIrNodeType *) ((GList*)ctx->type_stack->data)->data;
  if (ctx->type_parameters)
    param = (GIIrNodeType *) ctx->type_parameters->data;

  if (parent->tag == GI_TYPE_TAG_ARRAY ||
      parent->tag == GI_TYPE_TAG_GLIST ||
      parent->tag == GI_TYPE_TAG_GSLIST)
    {
      g_assert (param != NULL);

      if (parent->parameter_type1 == NULL)
        parent->parameter_type1 = param;
      else
        g_assert_not_reached ();
    }
  else if (parent->tag == GI_TYPE_TAG_GHASH)
    {
      g_assert (param != NULL);

      if (parent->parameter_type1 == NULL)
        parent->parameter_type1 = param;
      else if (parent->parameter_type2 == NULL)
        parent->parameter_type2 = param;
      else
        g_assert_not_reached ();
    }
  g_list_free (ctx->type_parameters);
  ctx->type_parameters = (GList *)ctx->type_stack->data;
  ctx->type_stack = g_list_delete_link (ctx->type_stack, ctx->type_stack);
}

static void
end_type (ParseContext *ctx)
{
  if (ctx->type_depth == 1)
    {
      end_type_top (ctx);
      state_switch (ctx, ctx->prev_state);
    }
  else
    {
      end_type_recurse (ctx);
      ctx->type_depth--;
    }
}

static gboolean
start_attribute (GMarkupParseContext  *context,
                 const char           *element_name,
                 const char          **attribute_names,
                 const char          **attribute_values,
                 ParseContext        *ctx,
                 GError              **error)
{
  const char *name;
  const char *value;
  GIIrNode *curnode;

  if (strcmp (element_name, "attribute") != 0 || ctx->node_stack == NULL)
    return FALSE;

  name = find_attribute ("name", attribute_names, attribute_values);
  value = find_attribute ("value", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }
  if (value == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "value");
      return FALSE;
    }

  state_switch (ctx, STATE_ATTRIBUTE);

  curnode = CURRENT_NODE (ctx);

  if (ctx->current_typed && ctx->current_typed->type == GI_IR_NODE_PARAM)
    {
      g_hash_table_insert (ctx->current_typed->attributes, g_strdup (name), g_strdup (value));
    }
  else
    {
      g_hash_table_insert (curnode->attributes, g_strdup (name), g_strdup (value));
    }

  return TRUE;
}

static gboolean
start_return_value (GMarkupParseContext  *context,
                    const char           *element_name,
                    const char          **attribute_names,
                    const char          **attribute_values,
                    ParseContext        *ctx,
                    GError              **error)
{
  GIIrNodeParam *param;
  const char *transfer;
  const char *skip;
  const char *nullable;

  if (!(strcmp (element_name, "return-value") == 0 &&
        ctx->state == STATE_FUNCTION))
    return FALSE;

  param = (GIIrNodeParam *)gi_ir_node_new (GI_IR_NODE_PARAM,
                                           ctx->current_module);
  param->in = FALSE;
  param->out = FALSE;
  param->retval = TRUE;

  ctx->current_typed = (GIIrNode*) param;

  state_switch (ctx, STATE_FUNCTION_RETURN);

  skip = find_attribute ("skip", attribute_names, attribute_values);
  if (skip && strcmp (skip, "1") == 0)
    param->skip = TRUE;
  else
    param->skip = FALSE;

  transfer = find_attribute ("transfer-ownership", attribute_names, attribute_values);
  if (!parse_param_transfer (param, transfer, NULL, error))
    return FALSE;

  nullable = find_attribute ("nullable", attribute_names, attribute_values);
  if (nullable && g_str_equal (nullable, "1"))
    param->nullable = TRUE;

  switch (CURRENT_NODE (ctx)->type)
    {
    case GI_IR_NODE_FUNCTION:
    case GI_IR_NODE_CALLBACK:
      {
        GIIrNodeFunction *func = (GIIrNodeFunction *)CURRENT_NODE (ctx);
        func->result = param;
      }
      break;
    case GI_IR_NODE_SIGNAL:
      {
        GIIrNodeSignal *signal = (GIIrNodeSignal *)CURRENT_NODE (ctx);
        signal->result = param;
      }
      break;
    case GI_IR_NODE_VFUNC:
      {
        GIIrNodeVFunc *vfunc = (GIIrNodeVFunc *)CURRENT_NODE (ctx);
        vfunc->result = param;
      }
      break;
    default:
      g_assert_not_reached ();
    }

  return TRUE;
}

static gboolean
start_implements (GMarkupParseContext  *context,
                  const char           *element_name,
                  const char          **attribute_names,
                  const char          **attribute_values,
                  ParseContext        *ctx,
                  GError              **error)
{
  GIIrNodeInterface *iface;
  const char *name;

  if (strcmp (element_name, "implements") != 0 ||
      !(ctx->state == STATE_CLASS))
    return FALSE;

  state_switch (ctx, STATE_IMPLEMENTS);

  name = find_attribute ("name", attribute_names, attribute_values);
  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }

  iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);
  iface->interfaces = g_list_append (iface->interfaces, g_strdup (name));

  return TRUE;
}

static gboolean
start_glib_signal (GMarkupParseContext  *context,
                   const char           *element_name,
                   const char          **attribute_names,
                   const char          **attribute_values,
                   ParseContext        *ctx,
                   GError              **error)
{
  const char *name;
  const char *when;
  const char *no_recurse;
  const char *detailed;
  const char *action;
  const char *no_hooks;
  const char *has_class_closure;
  GIIrNodeInterface *iface;
  GIIrNodeSignal *signal;

  if (!(strcmp (element_name, "glib:signal") == 0 &&
        (ctx->state == STATE_CLASS ||
         ctx->state == STATE_INTERFACE)))
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_FUNCTION))
    return TRUE;

  name = find_attribute ("name", attribute_names, attribute_values);
  when = find_attribute ("when", attribute_names, attribute_values);
  no_recurse = find_attribute ("no-recurse", attribute_names, attribute_values);
  detailed = find_attribute ("detailed", attribute_names, attribute_values);
  action = find_attribute ("action", attribute_names, attribute_values);
  no_hooks = find_attribute ("no-hooks", attribute_names, attribute_values);
  has_class_closure = find_attribute ("has-class-closure", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }
  signal = (GIIrNodeSignal *)gi_ir_node_new (GI_IR_NODE_SIGNAL,
                                             ctx->current_module);

  ((GIIrNode *)signal)->name = g_strdup (name);

  signal->run_first = FALSE;
  signal->run_last = FALSE;
  signal->run_cleanup = FALSE;
  if (when == NULL || g_ascii_strcasecmp (when, "LAST") == 0)
    signal->run_last = TRUE;
  else if (g_ascii_strcasecmp (when, "FIRST") == 0)
    signal->run_first = TRUE;
  else
    signal->run_cleanup = TRUE;

  if (no_recurse && strcmp (no_recurse, "1") == 0)
    signal->no_recurse = TRUE;
  else
    signal->no_recurse = FALSE;
  if (detailed && strcmp (detailed, "1") == 0)
    signal->detailed = TRUE;
  else
    signal->detailed = FALSE;
  if (action && strcmp (action, "1") == 0)
    signal->action = TRUE;
  else
    signal->action = FALSE;
  if (no_hooks && strcmp (no_hooks, "1") == 0)
    signal->no_hooks = TRUE;
  else
    signal->no_hooks = FALSE;
  if (has_class_closure && strcmp (has_class_closure, "1") == 0)
    signal->has_class_closure = TRUE;
  else
    signal->has_class_closure = FALSE;

  iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);
  iface->members = g_list_append (iface->members, signal);

  push_node (ctx, (GIIrNode *)signal);

  return TRUE;
}

static gboolean
start_vfunc (GMarkupParseContext  *context,
             const char           *element_name,
             const char          **attribute_names,
             const char          **attribute_values,
             ParseContext        *ctx,
             GError              **error)
{
  const char *name;
  const char *must_chain_up;
  const char *override;
  const char *is_class_closure;
  const char *offset;
  const char *invoker;
  const char *throws;
  GIIrNodeInterface *iface;
  GIIrNodeVFunc *vfunc;
  guint64 parsed_offset;

  if (!(strcmp (element_name, "virtual-method") == 0 &&
        (ctx->state == STATE_CLASS ||
         ctx->state == STATE_INTERFACE)))
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_FUNCTION))
    return TRUE;

  name = find_attribute ("name", attribute_names, attribute_values);
  must_chain_up = find_attribute ("must-chain-up", attribute_names, attribute_values);
  override = find_attribute ("override", attribute_names, attribute_values);
  is_class_closure = find_attribute ("is-class-closure", attribute_names, attribute_values);
  offset = find_attribute ("offset", attribute_names, attribute_values);
  invoker = find_attribute ("invoker", attribute_names, attribute_values);
  throws = find_attribute ("throws", attribute_names, attribute_values);

  if (name == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }

  vfunc = (GIIrNodeVFunc *)gi_ir_node_new (GI_IR_NODE_VFUNC,
                                           ctx->current_module);

  ((GIIrNode *)vfunc)->name = g_strdup (name);

  if (must_chain_up && strcmp (must_chain_up, "1") == 0)
    vfunc->must_chain_up = TRUE;
  else
    vfunc->must_chain_up = FALSE;

  if (override && strcmp (override, "always") == 0)
    {
      vfunc->must_be_implemented = TRUE;
      vfunc->must_not_be_implemented = FALSE;
    }
  else if (override && strcmp (override, "never") == 0)
    {
      vfunc->must_be_implemented = FALSE;
      vfunc->must_not_be_implemented = TRUE;
    }
  else
    {
      vfunc->must_be_implemented = FALSE;
      vfunc->must_not_be_implemented = FALSE;
    }

  if (is_class_closure && strcmp (is_class_closure, "1") == 0)
    vfunc->is_class_closure = TRUE;
  else
    vfunc->is_class_closure = FALSE;

  if (throws && strcmp (throws, "1") == 0)
    vfunc->throws = TRUE;
  else
    vfunc->throws = FALSE;

  if (offset == NULL)
    vfunc->offset = 0xFFFF;
  else if (g_ascii_string_to_unsigned (offset, 10, 0, G_MAXSIZE, &parsed_offset, error))
    vfunc->offset = parsed_offset;
  else
    {
      gi_ir_node_free ((GIIrNode *) vfunc);
      return FALSE;
    }

  vfunc->invoker = g_strdup (invoker);

  iface = (GIIrNodeInterface *)CURRENT_NODE (ctx);
  iface->members = g_list_append (iface->members, vfunc);

  push_node (ctx, (GIIrNode *)vfunc);

  return TRUE;
}

static gboolean
start_struct (GMarkupParseContext  *context,
              const char           *element_name,
              const char          **attribute_names,
              const char          **attribute_values,
              ParseContext         *ctx,
              GError               **error)
{
  const char *name;
  const char *deprecated;
  const char *disguised;
  const char *opaque;
  const char *pointer;
  const char *gtype_name;
  const char *gtype_init;
  const char *gtype_struct;
  const char *foreign;
  const char *copy_func;
  const char *free_func;
  GIIrNodeStruct *struct_;

  if (!(strcmp (element_name, "record") == 0 &&
        (ctx->state == STATE_NAMESPACE ||
         ctx->state == STATE_UNION ||
         ctx->state == STATE_STRUCT ||
         ctx->state == STATE_CLASS)))
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_STRUCT))
    return TRUE;

  name = find_attribute ("name", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);
  disguised = find_attribute ("disguised", attribute_names, attribute_values);
  pointer = find_attribute ("pointer", attribute_names, attribute_values);
  opaque = find_attribute ("opaque", attribute_names, attribute_values);
  gtype_name = find_attribute ("glib:type-name", attribute_names, attribute_values);
  gtype_init = find_attribute ("glib:get-type", attribute_names, attribute_values);
  gtype_struct = find_attribute ("glib:is-gtype-struct-for", attribute_names, attribute_values);
  foreign = find_attribute ("foreign", attribute_names, attribute_values);
  copy_func = find_attribute ("copy-function", attribute_names, attribute_values);
  free_func = find_attribute ("free-function", attribute_names, attribute_values);

  if (name == NULL && ctx->node_stack == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }
  if ((gtype_name == NULL && gtype_init != NULL))
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:type-name");
      return FALSE;
    }
  if ((gtype_name != NULL && gtype_init == NULL))
    {
      MISSING_ATTRIBUTE (context, error, element_name, "glib:get-type");
      return FALSE;
    }

  struct_ = (GIIrNodeStruct *) gi_ir_node_new (GI_IR_NODE_STRUCT,
                                               ctx->current_module);

  ((GIIrNode *)struct_)->name = g_strdup (name ? name : "");
  if (deprecated)
    struct_->deprecated = TRUE;
  else
    struct_->deprecated = FALSE;

  if (g_strcmp0 (disguised, "1") == 0)
    struct_->disguised = TRUE;

  if (g_strcmp0 (pointer, "1") == 0)
    struct_->pointer = TRUE;

  if (g_strcmp0 (opaque, "1") == 0)
    struct_->opaque = TRUE;

  struct_->is_gtype_struct = gtype_struct != NULL;

  struct_->gtype_name = g_strdup (gtype_name);
  struct_->gtype_init = g_strdup (gtype_init);

  struct_->foreign = (g_strcmp0 (foreign, "1") == 0);

  struct_->copy_func = g_strdup (copy_func);
  struct_->free_func = g_strdup (free_func);

  if (ctx->node_stack == NULL)
    ctx->current_module->entries =
      g_list_append (ctx->current_module->entries, struct_);
  push_node (ctx, (GIIrNode *)struct_);
  return TRUE;
}

static gboolean
start_union (GMarkupParseContext  *context,
             const char           *element_name,
             const char          **attribute_names,
             const char          **attribute_values,
             ParseContext        *ctx,
             GError              **error)
{
  const char *name;
  const char *deprecated;
  const char *typename;
  const char *typeinit;
  const char *copy_func;
  const char *free_func;
  GIIrNodeUnion *union_;

  if (!(strcmp (element_name, "union") == 0 &&
        (ctx->state == STATE_NAMESPACE ||
         ctx->state == STATE_UNION ||
         ctx->state == STATE_STRUCT ||
         ctx->state == STATE_CLASS)))
    return FALSE;

  if (!introspectable_prelude (context, attribute_names, attribute_values, ctx, STATE_UNION))
    return TRUE;

  name = find_attribute ("name", attribute_names, attribute_values);
  deprecated = find_attribute ("deprecated", attribute_names, attribute_values);
  typename = find_attribute ("glib:type-name", attribute_names, attribute_values);
  typeinit = find_attribute ("glib:get-type", attribute_names, attribute_values);
  copy_func = find_attribute ("copy-function", attribute_names, attribute_values);
  free_func = find_attribute ("free-function", attribute_names, attribute_values);

  if (name == NULL && ctx->node_stack == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "name");
      return FALSE;
    }

  union_ = (GIIrNodeUnion *) gi_ir_node_new (GI_IR_NODE_UNION,
                                             ctx->current_module);

  ((GIIrNode *)union_)->name = g_strdup (name ? name : "");
  union_->gtype_name = g_strdup (typename);
  union_->gtype_init = g_strdup (typeinit);
  union_->copy_func = g_strdup (copy_func);
  union_->free_func = g_strdup (free_func);
  if (deprecated)
    union_->deprecated = TRUE;
  else
    union_->deprecated = FALSE;

  if (ctx->node_stack == NULL)
    ctx->current_module->entries =
      g_list_append (ctx->current_module->entries, union_);
  push_node (ctx, (GIIrNode *)union_);
  return TRUE;
}

static gboolean
start_discriminator (GMarkupParseContext  *context,
                     const char           *element_name,
                     const char          **attribute_names,
                     const char          **attribute_values,
                     ParseContext         *ctx,
                     GError              **error)
{
  const char *type;
  const char *offset;
  guint64 parsed_offset;

  if (!(strcmp (element_name, "discriminator") == 0 &&
        ctx->state == STATE_UNION))
    return FALSE;

  type = find_attribute ("type", attribute_names, attribute_values);
  offset = find_attribute ("offset", attribute_names, attribute_values);
  if (type == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "type");
      return FALSE;
    }
  else if (offset == NULL)
    {
      MISSING_ATTRIBUTE (context, error, element_name, "offset");
      return FALSE;
    }

  ((GIIrNodeUnion *)CURRENT_NODE (ctx))->discriminator_type
    = parse_type (ctx, type);

  if (g_ascii_string_to_unsigned (offset, 10, 0, G_MAXSIZE, &parsed_offset, error))
    ((GIIrNodeUnion *)CURRENT_NODE (ctx))->discriminator_offset = parsed_offset;
  else
    return FALSE;

  return TRUE;
}

static gboolean
parse_include (GMarkupParseContext *context,
               ParseContext        *ctx,
               const char          *name,
               const char          *version)
{
  GError *error = NULL;
  char *buffer;
  gsize length;
  char *girpath, *girname;
  GIIrModule *module;
  GList *l;

  for (l = ctx->parser->parsed_modules; l; l = l->next)
    {
      GIIrModule *m = l->data;

      if (strcmp (m->name, name) == 0)
        {
          if (strcmp (m->version, version) == 0)
            {
              ctx->include_modules = g_list_prepend (ctx->include_modules, m);

              return TRUE;
            }
          else
            {
              g_printerr ("Module '%s' imported with conflicting versions '%s' and '%s'\n",
                          name, m->version, version);
              return FALSE;
            }
        }
    }

  girname = g_strdup_printf ("%s-%s.gir", name, version);
  girpath = locate_gir (ctx->parser, girname);

  if (girpath == NULL)
    {
      g_printerr ("Could not find GIR file '%s'; check XDG_DATA_DIRS or use --includedir\n",
                   girname);
      g_free (girname);
      return FALSE;
    }
  g_free (girname);

  g_debug ("Parsing include %s", girpath);

  if (!g_file_get_contents (girpath, &buffer, &length, &error))
    {
      g_printerr ("%s: %s\n", girpath, error->message);
      g_clear_error (&error);
      g_free (girpath);
      return FALSE;
    }

  module = gi_ir_parser_parse_string (ctx->parser, name, girpath, buffer, length, &error);
  g_free (buffer);
  if (error != NULL)
    {
      int line_number, char_number;
      g_markup_parse_context_get_position (context, &line_number, &char_number);
      g_printerr ("%s:%d:%d: error: %s\n", girpath, line_number, char_number, error->message);
      g_clear_error (&error);
      g_free (girpath);
      return FALSE;
    }
  g_free (girpath);

  ctx->include_modules = g_list_append (ctx->include_modules,
                                        module);

  return TRUE;
}

static void
start_element_handler (GMarkupParseContext  *context,
                       const char           *element_name,
                       const char          **attribute_names,
                       const char          **attribute_values,
                       gpointer              user_data,
                       GError              **error)
{
  ParseContext *ctx = user_data;

  if (ctx->parser->logged_levels & G_LOG_LEVEL_DEBUG)
    {
      GString *tags = g_string_new ("");
      int i;
      for (i = 0; attribute_names[i]; i++)
        g_string_append_printf (tags, "%s=\"%s\" ",
                                attribute_names[i],
                                attribute_values[i]);

      if (i)
        {
          g_string_insert_c (tags, 0, ' ');
          g_string_truncate (tags, tags->len - 1);
        }
      g_debug ("<%s%s>", element_name, tags->str);
      g_string_free (tags, TRUE);
    }

  if (ctx->state == STATE_PASSTHROUGH)
    {
      ctx->unknown_depth += 1;
      return;
    }

  switch (element_name[0])
    {
    case 'a':
      if (ctx->state == STATE_NAMESPACE && strcmp (element_name, "alias") == 0)
        {
          state_switch (ctx, STATE_ALIAS);
          goto out;
        }
      if (start_type (context, element_name,
                      attribute_names, attribute_values,
                      ctx, error))
        goto out;
      else if (start_attribute (context, element_name,
                                attribute_names, attribute_values,
                                ctx, error))
        goto out;
      break;
    case 'b':
      if (start_enum (context, element_name,
                      attribute_names, attribute_values,
                      ctx, error))
        goto out;
      break;
    case 'c':
      if (start_function (context, element_name,
                          attribute_names, attribute_values,
                          ctx, error))
        goto out;
      else if (start_constant (context, element_name,
                               attribute_names, attribute_values,
                               ctx, error))
        goto out;
      else if (start_class (context, element_name,
                            attribute_names, attribute_values,
                            ctx, error))
        goto out;
      break;

    case 'd':
      if (start_discriminator (context, element_name,
                               attribute_names, attribute_values,
                               ctx, error))
    goto out;
      if (strcmp ("doc", element_name) == 0 || strcmp ("doc-deprecated", element_name) == 0 ||
          strcmp ("doc-stability", element_name) == 0 || strcmp ("doc-version", element_name) == 0 ||
          strcmp ("docsection", element_name) == 0)
        {
          state_switch (ctx, STATE_PASSTHROUGH);
          goto out;
        }
      break;

    case 'e':
      if (start_enum (context, element_name,
                      attribute_names, attribute_values,
                      ctx, error))
        goto out;
      break;

    case 'f':
      if (strcmp ("function-macro", element_name) == 0)
        {
          state_switch (ctx, STATE_PASSTHROUGH);
          goto out;
        }
      else if (start_function (context, element_name,
                          attribute_names, attribute_values,
                          ctx, error))
        goto out;
      else if (start_field (context, element_name,
                            attribute_names, attribute_values,
                            ctx, error))
        goto out;
      break;

    case 'g':
      if (start_glib_boxed (context, element_name,
                            attribute_names, attribute_values,
                            ctx, error))
        goto out;
      else if (start_glib_signal (context, element_name,
                             attribute_names, attribute_values,
                             ctx, error))
        goto out;
      break;

    case 'i':
      if (strcmp (element_name, "include") == 0 &&
          ctx->state == STATE_REPOSITORY)
        {
          const char *name;
          const char *version;

          name = find_attribute ("name", attribute_names, attribute_values);
          version = find_attribute ("version", attribute_names, attribute_values);

          if (name == NULL)
            {
              MISSING_ATTRIBUTE (context, error, element_name, "name");
              break;
            }
          if (version == NULL)
            {
              MISSING_ATTRIBUTE (context, error, element_name, "version");
              break;
            }

          if (!parse_include (context, ctx, name, version))
            {
              g_set_error (error,
                           G_MARKUP_ERROR,
                           G_MARKUP_ERROR_INVALID_CONTENT,
                           "Failed to parse included gir %s-%s",
                           name,
                           version);
              return;
            }

          g_ptr_array_insert (ctx->dependencies, 0,
                              g_strdup_printf ("%s-%s", name, version));

          state_switch (ctx, STATE_INCLUDE);
          goto out;
        }
      if (start_interface (context, element_name,
                           attribute_names, attribute_values,
                           ctx, error))
        goto out;
      else if (start_implements (context, element_name,
                                 attribute_names, attribute_values,
                                 ctx, error))
        goto out;
      else if (start_instance_parameter (context, element_name,
                                attribute_names, attribute_values,
                                ctx, error))
        goto out;
      else if (strcmp (element_name, "c:include") == 0)
        {
          state_switch (ctx, STATE_C_INCLUDE);
          goto out;
        }
      break;

    case 'm':
      if (start_function (context, element_name,
                          attribute_names, attribute_values,
                          ctx, error))
        goto out;
      else if (start_member (context, element_name,
                          attribute_names, attribute_values,
                          ctx, error))
        goto out;
      break;

    case 'n':
      if (strcmp (element_name, "namespace") == 0 && ctx->state == STATE_REPOSITORY)
        {
          const char *name, *version, *shared_library, *cprefix;

          if (ctx->current_module != NULL)
            {
              g_set_error (error,
                           G_MARKUP_ERROR,
                           G_MARKUP_ERROR_INVALID_CONTENT,
                           "Only one <namespace/> element is currently allowed per <repository/>");
              goto out;
            }

          name = find_attribute ("name", attribute_names, attribute_values);
          version = find_attribute ("version", attribute_names, attribute_values);
          shared_library = find_attribute ("shared-library", attribute_names, attribute_values);
          cprefix = find_attribute ("c:identifier-prefixes", attribute_names, attribute_values);
          /* Backwards compatibility; vala currently still generates this */
          if (cprefix == NULL)
            cprefix = find_attribute ("c:prefix", attribute_names, attribute_values);

          if (name == NULL)
            MISSING_ATTRIBUTE (context, error, element_name, "name");
          else if (version == NULL)
            MISSING_ATTRIBUTE (context, error, element_name, "version");
          else
            {
              GList *l;

              if (strcmp (name, ctx->namespace) != 0)
                g_set_error (error,
                             G_MARKUP_ERROR,
                             G_MARKUP_ERROR_INVALID_CONTENT,
                             "<namespace/> name element '%s' doesn't match file name '%s'",
                             name, ctx->namespace);

              ctx->current_module = gi_ir_module_new (name, version, shared_library, cprefix);

              ctx->current_module->aliases = ctx->aliases;
              ctx->aliases = NULL;
              ctx->current_module->disguised_structures = ctx->disguised_structures;
              ctx->current_module->pointer_structures = ctx->pointer_structures;
              ctx->disguised_structures = NULL;
              ctx->pointer_structures = NULL;

              for (l = ctx->include_modules; l; l = l->next)
                gi_ir_module_add_include_module (ctx->current_module, l->data);

              g_list_free (ctx->include_modules);
              ctx->include_modules = NULL;

              ctx->modules = g_list_append (ctx->modules, ctx->current_module);

              if (ctx->current_module->dependencies != ctx->dependencies)
                {
                  g_clear_pointer (&ctx->current_module->dependencies, g_ptr_array_unref);
                  ctx->current_module->dependencies = g_ptr_array_ref (ctx->dependencies);
                }

              state_switch (ctx, STATE_NAMESPACE);
              goto out;
            }
        }
      break;

    case 'p':
      if (start_property (context, element_name,
                          attribute_names, attribute_values,
                          ctx, error))
        goto out;
      else if (strcmp (element_name, "parameters") == 0 &&
               ctx->state == STATE_FUNCTION)
        {
          state_switch (ctx, STATE_FUNCTION_PARAMETERS);

          goto out;
        }
      else if (start_parameter (context, element_name,
                                attribute_names, attribute_values,
                                ctx, error))
        goto out;
      else if (strcmp (element_name, "prerequisite") == 0 &&
               ctx->state == STATE_INTERFACE)
        {
          const char *name;

          name = find_attribute ("name", attribute_names, attribute_values);

          state_switch (ctx, STATE_PREREQUISITE);

          if (name == NULL)
            MISSING_ATTRIBUTE (context, error, element_name, "name");
          else
            {
              GIIrNodeInterface *iface;

              iface = (GIIrNodeInterface *)CURRENT_NODE(ctx);
              iface->prerequisites = g_list_append (iface->prerequisites, g_strdup (name));
            }
          goto out;
        }
      else if (strcmp (element_name, "package") == 0 &&
          ctx->state == STATE_REPOSITORY)
        {
          state_switch (ctx, STATE_PACKAGE);
          goto out;
        }
      break;

    case 'r':
      if (strcmp (element_name, "repository") == 0 && ctx->state == STATE_START)
        {
          const char *version;

          version = find_attribute ("version", attribute_names, attribute_values);

          if (version == NULL)
            MISSING_ATTRIBUTE (context, error, element_name, "version");
          else if (strcmp (version, SUPPORTED_GIR_VERSION) != 0)
            g_set_error (error,
                         G_MARKUP_ERROR,
                         G_MARKUP_ERROR_INVALID_CONTENT,
                         "Unsupported version '%s'",
                         version);
          else
            state_switch (ctx, STATE_REPOSITORY);

          goto out;
        }
      else if (start_return_value (context, element_name,
                                   attribute_names, attribute_values,
                                   ctx, error))
        goto out;
      else if (start_struct (context, element_name,
                             attribute_names, attribute_values,
                             ctx, error))
        goto out;
      break;

    case 's':
      if (strcmp ("source-position", element_name) == 0)
      {
          state_switch (ctx, STATE_PASSTHROUGH);
          goto out;
      }
      break;
    case 'u':
      if (start_union (context, element_name,
                       attribute_names, attribute_values,
                       ctx, error))
        goto out;
      break;

    case 't':
      if (start_type (context, element_name,
                      attribute_names, attribute_values,
                      ctx, error))
        goto out;
      break;

    case 'v':
      if (start_vfunc (context, element_name,
                       attribute_names, attribute_values,
                       ctx, error))
        goto out;
      if (start_type (context, element_name,
                      attribute_names, attribute_values,
                      ctx, error))
        goto out;
      break;
    default:
      break;
    }

  if (*error == NULL && ctx->state != STATE_PASSTHROUGH)
    {
      int line_number, char_number;
      g_markup_parse_context_get_position (context, &line_number, &char_number);
      if (!g_str_has_prefix (element_name, "c:"))
        g_printerr ("%s:%d:%d: warning: element %s from state %d is unknown, ignoring\n",
                    ctx->file_path, line_number, char_number, element_name,
                    ctx->state);
      state_switch (ctx, STATE_PASSTHROUGH);
    }

 out:
  if (*error)
    {
      int line_number, char_number;
      g_markup_parse_context_get_position (context, &line_number, &char_number);

      g_printerr ("%s:%d:%d: error: %s\n", ctx->file_path, line_number, char_number, (*error)->message);
    }
}

static gboolean
require_one_of_end_elements (GMarkupParseContext  *context,
                             ParseContext         *ctx,
                             const char           *actual_name,
                             GError              **error,
                             ...)
{
  va_list args;
  int line_number, char_number;
  const char *expected;
  gboolean matched = FALSE;

  va_start (args, error);

  while ((expected = va_arg (args, const char*)) != NULL)
    {
      if (strcmp (expected, actual_name) == 0)
        {
          matched = TRUE;
          break;
        }
    }

  va_end (args);

  if (matched)
    return TRUE;

  g_markup_parse_context_get_position (context, &line_number, &char_number);
  g_set_error (error,
               G_MARKUP_ERROR,
               G_MARKUP_ERROR_INVALID_CONTENT,
               "Unexpected end tag '%s' on line %d char %d; current state=%d (prev=%d)",
               actual_name,
               line_number, char_number, ctx->state, ctx->prev_state);
  return FALSE;
}

static gboolean
state_switch_end_struct_or_union (GMarkupParseContext  *context,
                                  ParseContext         *ctx,
                                  const char           *element_name,
                                  GError              **error)
{
  GIIrNode *node = pop_node (ctx);

  if (ctx->node_stack == NULL)
    {
      state_switch (ctx, STATE_NAMESPACE);
    }
  else
    {
      /* In this case the node was not tracked by any other node, so we need
       * to free the node, or we'd leak.
       */
      g_clear_pointer (&node, gi_ir_node_free);

      if (CURRENT_NODE (ctx)->type == GI_IR_NODE_STRUCT)
        state_switch (ctx, STATE_STRUCT);
      else if (CURRENT_NODE (ctx)->type == GI_IR_NODE_UNION)
        state_switch (ctx, STATE_UNION);
      else if (CURRENT_NODE (ctx)->type == GI_IR_NODE_OBJECT)
        state_switch (ctx, STATE_CLASS);
      else
        {
          int line_number, char_number;
          g_markup_parse_context_get_position (context, &line_number, &char_number);
          g_set_error (error,
                       G_MARKUP_ERROR,
                       G_MARKUP_ERROR_INVALID_CONTENT,
                       "Unexpected end tag '%s' on line %d char %d",
                       element_name,
                       line_number, char_number);
          return FALSE;
        }
    }
  return TRUE;
}

static gboolean
require_end_element (GMarkupParseContext  *context,
                     ParseContext         *ctx,
                     const char           *expected_name,
                     const char           *actual_name,
                     GError              **error)
{
  return require_one_of_end_elements (context, ctx, actual_name, error, expected_name, NULL);
}

static void
end_element_handler (GMarkupParseContext  *context,
                     const char           *element_name,
                     gpointer              user_data,
                     GError              **error)
{
  ParseContext *ctx = user_data;

  g_debug ("</%s>", element_name);

  switch (ctx->state)
    {
    case STATE_START:
    case STATE_END:
      /* no need to GError here, GMarkup already catches this */
      break;

    case STATE_REPOSITORY:
      state_switch (ctx, STATE_END);
      break;

    case STATE_INCLUDE:
      if (require_end_element (context, ctx, "include", element_name, error))
        {
          state_switch (ctx, STATE_REPOSITORY);
        }
      break;

    case STATE_C_INCLUDE:
      if (require_end_element (context, ctx, "c:include", element_name, error))
        {
          state_switch (ctx, STATE_REPOSITORY);
        }
      break;

    case STATE_PACKAGE:
      if (require_end_element (context, ctx, "package", element_name, error))
        {
          state_switch (ctx, STATE_REPOSITORY);
        }
      break;

    case STATE_NAMESPACE:
      if (require_end_element (context, ctx, "namespace", element_name, error))
        {
          ctx->current_module = NULL;
          state_switch (ctx, STATE_REPOSITORY);
        }
      break;

    case STATE_ALIAS:
      if (require_end_element (context, ctx, "alias", element_name, error))
        {
          g_free (ctx->current_alias);
          ctx->current_alias = NULL;
          state_switch (ctx, STATE_NAMESPACE);
        }
      break;

    case STATE_FUNCTION_RETURN:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "return-value", element_name, error))
        {
          state_switch (ctx, STATE_FUNCTION);
        }
      break;

    case STATE_FUNCTION_PARAMETERS:
      if (require_end_element (context, ctx, "parameters", element_name, error))
        {
          state_switch (ctx, STATE_FUNCTION);
        }
      break;

    case STATE_FUNCTION_PARAMETER:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "parameter", element_name, error))
        {
          state_switch (ctx, STATE_FUNCTION_PARAMETERS);
        }
      break;

    case STATE_FUNCTION:
      {
        pop_node (ctx);
        if (ctx->node_stack == NULL)
          {
            state_switch (ctx, STATE_NAMESPACE);
          }
        else
          {
            g_debug("case STATE_FUNCTION %d", CURRENT_NODE (ctx)->type);
            if (ctx->in_embedded_state != STATE_NONE)
              {
                state_switch (ctx, ctx->in_embedded_state);
                ctx->in_embedded_state = STATE_NONE;
              }
            else if (CURRENT_NODE (ctx)->type == GI_IR_NODE_INTERFACE)
              state_switch (ctx, STATE_INTERFACE);
            else if (CURRENT_NODE (ctx)->type == GI_IR_NODE_OBJECT)
              state_switch (ctx, STATE_CLASS);
            else if (CURRENT_NODE (ctx)->type == GI_IR_NODE_BOXED)
              state_switch (ctx, STATE_BOXED);
            else if (CURRENT_NODE (ctx)->type == GI_IR_NODE_STRUCT)
              state_switch (ctx, STATE_STRUCT);
            else if (CURRENT_NODE (ctx)->type == GI_IR_NODE_UNION)
              state_switch (ctx, STATE_UNION);
            else if (CURRENT_NODE (ctx)->type == GI_IR_NODE_ENUM ||
                     CURRENT_NODE (ctx)->type == GI_IR_NODE_FLAGS)
              state_switch (ctx, STATE_ENUM);
            else
              {
                int line_number, char_number;
                g_markup_parse_context_get_position (context, &line_number, &char_number);
                g_set_error (error,
                             G_MARKUP_ERROR,
                             G_MARKUP_ERROR_INVALID_CONTENT,
                             "Unexpected end tag '%s' on line %d char %d",
                             element_name,
                             line_number, char_number);
              }
          }
      }
      break;

    case STATE_CLASS_FIELD:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "field", element_name, error))
        {
          state_switch (ctx, STATE_CLASS);
        }
      break;

    case STATE_CLASS_PROPERTY:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "property", element_name, error))
        {
          state_switch (ctx, STATE_CLASS);
        }
      break;

    case STATE_CLASS:
      if (require_end_element (context, ctx, "class", element_name, error))
        {
          pop_node (ctx);
          state_switch (ctx, STATE_NAMESPACE);
        }
      break;

    case STATE_INTERFACE_PROPERTY:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "property", element_name, error))
        {
          state_switch (ctx, STATE_INTERFACE);
        }
      break;

    case STATE_INTERFACE_FIELD:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "field", element_name, error))
        {
          state_switch (ctx, STATE_INTERFACE);
        }
      break;

    case STATE_INTERFACE:
      if (require_end_element (context, ctx, "interface", element_name, error))
        {
          pop_node (ctx);
          state_switch (ctx, STATE_NAMESPACE);
        }
      break;

    case STATE_ENUM:
      if (strcmp ("member", element_name) == 0)
        break;
      else if (strcmp ("function", element_name) == 0)
        break;
      else if (require_one_of_end_elements (context, ctx,
                                            element_name, error, "enumeration",
                                            "bitfield", NULL))
        {
          pop_node (ctx);
          state_switch (ctx, STATE_NAMESPACE);
        }
      break;

    case STATE_BOXED:
      if (require_end_element (context, ctx, "glib:boxed", element_name, error))
        {
          pop_node (ctx);
          state_switch (ctx, STATE_NAMESPACE);
        }
      break;

    case STATE_BOXED_FIELD:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "field", element_name, error))
        {
          state_switch (ctx, STATE_BOXED);
        }
      break;

    case STATE_STRUCT_FIELD:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "field", element_name, error))
        {
          state_switch (ctx, STATE_STRUCT);
        }
      break;

    case STATE_STRUCT:
      if (require_end_element (context, ctx, "record", element_name, error))
        {
          state_switch_end_struct_or_union (context, ctx, element_name, error);
        }
      break;

    case STATE_UNION_FIELD:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "field", element_name, error))
        {
          state_switch (ctx, STATE_UNION);
        }
      break;

    case STATE_UNION:
      if (require_end_element (context, ctx, "union", element_name, error))
        {
          state_switch_end_struct_or_union (context, ctx, element_name, error);
        }
      break;
    case STATE_IMPLEMENTS:
      if (strcmp ("interface", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "implements", element_name, error))
        state_switch (ctx, STATE_CLASS);
      break;
    case STATE_PREREQUISITE:
      if (require_end_element (context, ctx, "prerequisite", element_name, error))
        state_switch (ctx, STATE_INTERFACE);
      break;
    case STATE_NAMESPACE_CONSTANT:
    case STATE_CLASS_CONSTANT:
    case STATE_INTERFACE_CONSTANT:
      if (strcmp ("type", element_name) == 0)
        break;
      if (require_end_element (context, ctx, "constant", element_name, error))
        {
          switch (ctx->state)
            {
            case STATE_NAMESPACE_CONSTANT:
                    pop_node (ctx);
              state_switch (ctx, STATE_NAMESPACE);
              break;
            case STATE_CLASS_CONSTANT:
              state_switch (ctx, STATE_CLASS);
              break;
            case STATE_INTERFACE_CONSTANT:
              state_switch (ctx, STATE_INTERFACE);
              break;
            default:
              g_assert_not_reached ();
              break;
            }
        }
      break;
    case STATE_TYPE:
      if ((strcmp ("type", element_name) == 0) || (strcmp ("array", element_name) == 0) ||
          (strcmp ("varargs", element_name) == 0))
        {
          end_type (ctx);
        }
      break;
    case STATE_ATTRIBUTE:
      if (strcmp ("attribute", element_name) == 0)
        {
          state_switch (ctx, ctx->prev_state);
        }
      break;

    case STATE_PASSTHROUGH:
      ctx->unknown_depth -= 1;
      g_assert (ctx->unknown_depth >= 0);
      if (ctx->unknown_depth == 0)
        state_switch (ctx, ctx->prev_state);
      break;
    default:
      g_error ("Unhandled state %d in end_element_handler", ctx->state);
    }
}

static void
text_handler (GMarkupParseContext  *context,
              const char           *text,
              gsize                 text_len,
              gpointer              user_data,
              GError              **error)
{
  /* FIXME warn about non-whitespace text */
}

static void
cleanup (GMarkupParseContext *context,
         GError              *error,
         void                *user_data)
{
  ParseContext *ctx = user_data;
  GList *m;

  g_clear_slist (&ctx->node_stack, NULL);

  for (m = ctx->modules; m; m = m->next)
    gi_ir_module_free (m->data);
  g_list_free (ctx->modules);
  ctx->modules = NULL;

  ctx->current_module = NULL;
}

/**
 * gi_ir_parser_parse_string:
 * @parser: a #GIIrParser
 * @namespace: the namespace of the string
 * @filename: (nullable) (type filename): Path to parsed file, or `NULL`
 * @buffer: (array length=length): the data containing the XML
 * @length: length of the data, in bytes
 * @error: return location for a [type@GLib.Error], or `NULL`
 *
 * Parse a string that holds a complete GIR XML file, and return a list of a
 * a [class@GIRepository.IrModule] for each `<namespace/>` element within the
 * file.
 *
 * Returns: (transfer none): a new [class@GIRepository.IrModule]
 * Since: 2.80
 */
GIIrModule *
gi_ir_parser_parse_string (GIIrParser   *parser,
                           const char   *namespace,
                           const char   *filename,
                           const char   *buffer,
                           gssize        length,
                           GError      **error)
{
  ParseContext ctx = { 0 };
  GMarkupParseContext *context;
  GIIrModule *module = NULL;

  ctx.parser = parser;
  ctx.state = STATE_START;
  ctx.file_path = filename;
  ctx.namespace = namespace;
  ctx.include_modules = NULL;
  ctx.aliases = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
  ctx.disguised_structures = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  ctx.pointer_structures = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  ctx.type_depth = 0;
  ctx.dependencies = g_ptr_array_new_with_free_func (g_free);
  ctx.current_module = NULL;

  context = g_markup_parse_context_new (&firstpass_parser, 0, &ctx, NULL);

  if (!g_markup_parse_context_parse (context, buffer, length, error))
    goto out;

  if (!g_markup_parse_context_end_parse (context, error))
    goto out;

  g_markup_parse_context_free (context);

  ctx.state = STATE_START;
  context = g_markup_parse_context_new (&markup_parser, 0, &ctx, NULL);
  if (!g_markup_parse_context_parse (context, buffer, length, error))
    goto out;

  if (!g_markup_parse_context_end_parse (context, error))
    goto out;

  if (ctx.modules)
    module = ctx.modules->data;

  parser->parsed_modules = g_list_concat (g_steal_pointer (&ctx.modules),
                                          parser->parsed_modules);

 out:

  if (module == NULL)
    {
      /* An error occurred before we created a module, so we haven't
       * transferred ownership of these hash tables to the module.
       */
      g_clear_pointer (&ctx.aliases, g_hash_table_unref);
      g_clear_pointer (&ctx.disguised_structures, g_hash_table_unref);
      g_clear_pointer (&ctx.pointer_structures, g_hash_table_unref);
      g_clear_list (&ctx.modules, (GDestroyNotify) gi_ir_module_free);
      g_list_free (ctx.include_modules);
    }

  g_clear_slist (&ctx.node_stack, NULL);
  g_clear_pointer (&ctx.dependencies, g_ptr_array_unref);
  g_markup_parse_context_free (context);

  if (module)
    return module;

  if (error && *error == NULL)
    g_set_error (error,
                 G_MARKUP_ERROR,
                 G_MARKUP_ERROR_INVALID_CONTENT,
                 "Expected namespace element in the gir file");
  return NULL;
}

/**
 * gi_ir_parser_parse_file:
 * @parser: a #GIIrParser
 * @filename: (type filename): filename to parse
 * @error: return location for a [type@GLib.Error], or `NULL`
 *
 * Parse the given GIR XML file, and return a list of a
 * [class@GIRepository.IrModule] for each `<namespace/>` element within the
 * file.
 *
 * Returns: (transfer container): a newly allocated list of
 *   [class@GIRepository.IrModule]s. The modules themselves
 *   are owned by the `GIIrParser` and will be freed along with the parser.
 * Since: 2.80
 */
GIIrModule *
gi_ir_parser_parse_file (GIIrParser   *parser,
                         const char   *filename,
                         GError      **error)
{
  char *buffer;
  gsize length;
  GIIrModule *module;
  char *dash;
  char *namespace;

  if (!g_str_has_suffix (filename, ".gir"))
    {
      g_set_error (error,
                   G_MARKUP_ERROR,
                   G_MARKUP_ERROR_INVALID_CONTENT,
                   "Expected filename to end with '.gir'");
      return NULL;
    }

  g_debug ("[parsing] filename %s", filename);

  namespace = g_path_get_basename (filename);
  namespace[strlen(namespace)-4] = '\0';

  /* Remove version */
  dash = strstr (namespace, "-");
  if (dash != NULL)
    *dash = '\0';

  if (!g_file_get_contents (filename, &buffer, &length, error))
    {
      g_free (namespace);

      return NULL;
    }

  module = gi_ir_parser_parse_string (parser, namespace, filename, buffer, length, error);

  g_free (namespace);

  g_free (buffer);

  return module;
}


