diff options
author | Ryan Lortie <desrt@desrt.ca> | 2013-11-05 12:25:35 -0500 |
---|---|---|
committer | Ryan Lortie <desrt@desrt.ca> | 2013-11-05 12:25:35 -0500 |
commit | 6d26f2dafdccaeeeaac09b40bcf84489c6e76329 (patch) | |
tree | eb2a5260dc54d51fd7594d3818ae3983bc8eaac1 | |
parent | b3bee1d8d60d73fecc9625de302e56d48bc988fa (diff) | |
download | glib-wip/new-parser.tar.gz |
new compiler add filewip/new-parser
wip
-rw-r--r-- | gio/new-compiler.c | 1237 |
1 files changed, 1237 insertions, 0 deletions
diff --git a/gio/new-compiler.c b/gio/new-compiler.c new file mode 100644 index 000000000..92a7a0e57 --- /dev/null +++ b/gio/new-compiler.c @@ -0,0 +1,1237 @@ +/* + * Copyright © 2010 Codethink Limited + * Copyright © 2013 Canonical Limited + * + * 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 licence, 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. + * + * Author: Ryan Lortie <desrt@desrt.ca> + */ + +#define _(x)(x) + +#include <gio/gio.h> +#include "gvdb/gvdb-builder.h" +#include "strinfo.c" + +/* Forward declarations {{{1 */ +typedef struct _FileRef FileRef; +typedef struct _Enum Enum; +typedef struct _Schema Schema; +typedef struct _Override Override; +typedef struct _Key Key; +typedef struct _Dir Dir; + +static Schema * dir_resolve_schema (Dir *dir, + const gchar *id, + const gchar *detail, + const gchar *purpose, + const gchar *caller, + GError **error); +static Enum * dir_resolve_enum (Dir *dir, + const gchar *id, + gboolean is_flags, + const gchar *for_key, + const gchar *of_schema, + GError **error); +static Enum * schema_resolve_enum (Schema *schema, + const gchar *id, + gboolean is_flags, + const gchar *for_key, + GError **error); + +static gboolean dir_add_enum (Dir *dir, + GMarkupReader *reader, + const gchar *id, + Enum *enum_, + GError **error); + +static gboolean dir_add_schema (Dir *dir, + GMarkupReader *reader, + const gchar *id, + Schema *schema, + GError **error); + +static gboolean schema_add_key (Schema *schema, + GMarkupReader *reader, + const gchar *name, + Key *key, + GError **error); + +/* <enum> and <flags> {{{1 */ + +struct _Enum +{ + Dir *dir; + gchar *id; + gboolean is_flags; + GString *strinfo; +}; + +static void +enum_free (gpointer data) +{ + Enum *enum_ = data; + + g_string_free (enum_->strinfo, TRUE); + g_free (enum_->id); + + g_slice_free (Enum, enum_); +} + +static gboolean +enum_parse_value (GMarkupReader *reader, + GCancellable *cancellable, + Enum *enum_, + GError **error) +{ + const gchar *nick, *valuestr; + gint64 value; + gchar *end; + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRING, "nick", &nick, + G_MARKUP_COLLECT_STRING, "value", &valuestr, + G_MARKUP_COLLECT_INVALID)) + return FALSE; + + if (nick[0] == '\0' || nick[1] == '\0') + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "nick must be a minimum of 2 characters"); + return FALSE; + } + + value = g_ascii_strtoll (valuestr, &end, 0); + if (*end || enum_->is_flags ? + (value > G_MAXUINT32 || value < 0) : + (value > G_MAXINT32 || value < G_MININT32)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "invalid numeric value"); + return FALSE; + } + + if (strinfo_builder_contains (enum_->strinfo, nick)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<value nick='%s'/> already specified", nick); + return FALSE; + } + + if (strinfo_builder_contains_value (enum_->strinfo, value)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "value='%s' already specified", valuestr); + return FALSE; + } + + /* Silently drop the null case if it is mentioned. + * It is properly denoted with an empty array. + */ + if (enum_->is_flags && value == 0) + return TRUE; + + if (enum_->is_flags && (value & (value - 1))) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "flags values must have at most 1 bit set"); + return FALSE; + } + + /* Since we reject exact duplicates of value='' and we only allow one + * bit to be set, it's not possible to have overlaps. + * + * If we loosen the one-bit-set restriction we need an overlap check. + */ + + strinfo_builder_append_item (enum_->strinfo, nick, value); + + return g_markup_reader_expect_end (reader, NULL, error); +} + +static gboolean +enum_parse (GMarkupReader *reader, + GCancellable *cancellable, + Dir *dir, + GError **error) +{ + Enum *enum_; + + enum_ = g_slice_new0 (Enum); + enum_->is_flags = g_markup_reader_is_start_element (reader, "flags"); + enum_->strinfo = g_string_new (NULL); + + g_assert (enum_->is_flags || g_markup_reader_is_start_element (reader, "enum")); + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRDUP, "id", &enum_->id, + G_MARKUP_COLLECT_INVALID)) + goto error; + + if (!g_markup_reader_collect_elements (reader, cancellable, enum_, error, "value", enum_parse_value, NULL)) + goto error; + + if (!dir_add_enum (dir, reader, enum_->id, enum_, error)) + goto error; + + return TRUE; + +error: + enum_free (enum_); + + return FALSE; +} + +/* <key> {{{1 */ + +struct _Key +{ + Schema *schema; + const gchar *name; + + gchar *type_string; + GVariantType *type; + gchar *enum_name; + Enum *enum_; + gchar *flags_name; + Enum *flags; + + gchar l10n; + gchar *l10n_context; + gchar *default_text; + GVariant *default_value; + + GString *strinfo; + gboolean is_enum; + gboolean is_flags; + + GVariant *minimum; + GVariant *maximum; + + gboolean has_choices; + gboolean has_aliases; + gboolean is_override; + + gboolean checked; + GVariant *serialised; +}; + +static void +key_free (gpointer data) +{ + Key *key = data; + + g_slice_free (Key, key); +} + +static gboolean +key_resolve (Key *key, + GError **error) +{ + if (key->enum_name) + { + key->enum_ = schema_resolve_enum (key->schema, key->enum_name, FALSE, key->name, error); + if (key->enum_ == NULL) + return FALSE; + } + + if (key->flags_name) + { + key->flags = schema_resolve_enum (key->schema, key->flags_name, TRUE, key->name, error); + if (key->flags == NULL) + return FALSE; + } + + return TRUE; +} + +static gboolean +key_parse_default (GMarkupReader *reader, + GCancellable *cancellable, + Key *key, + GError **error) +{ + if (key->default_value) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, "<default/> must be specified exactly once"); + return FALSE; + } + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "l10n", &key->l10n, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "context", &key->l10n_context, + G_MARKUP_COLLECT_INVALID)) + return FALSE; + + key->default_text = g_markup_reader_collect_text (reader, cancellable, error); + + return key->default_text != NULL; +} + +static gboolean +key_parse_range (GMarkupReader *reader, + GCancellable *cancellable, + Key *key, + GError **error) +{ + const gchar *min_str, *max_str; + const struct { + const gchar type; + const gchar *min; + const gchar *max; + } table[] = { + { 'y', "0", "255" }, + { 'n', "-32768", "32767" }, + { 'q', "0", "65535" }, + { 'i', "-2147483648", "2147483647" }, + { 'u', "0", "4294967295" }, + { 'x', "-9223372036854775808", "9223372036854775807" }, + { 't', "0", "18446744073709551615" }, + { 'd', "-inf", "inf" }, + }; + gboolean type_ok = FALSE; + gint i; + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "min", &min_str, + G_MARKUP_COLLECT_STRING | G_MARKUP_COLLECT_OPTIONAL, "max", &max_str, + G_MARKUP_COLLECT_INVALID)) + return FALSE; + + if (key->minimum) + { + g_set_error_literal (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<range/> already specified for this key"); + return FALSE; + } + + for (i = 0; i < G_N_ELEMENTS (table); i++) + if (*(char *) key->type == table[i].type) + { + min_str = min_str ? min_str : table[i].min; + max_str = max_str ? max_str : table[i].max; + type_ok = TRUE; + break; + } + + if (!type_ok) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "<range> not allowed for keys of type '%s'", key->type_string); + return FALSE; + } + + key->minimum = g_variant_parse (key->type, min_str, NULL, NULL, error); + if (key->minimum == NULL) + return FALSE; + + key->maximum = g_variant_parse (key->type, max_str, NULL, NULL, error); + if (key->maximum == NULL) + return FALSE; + + if (g_variant_compare (key->minimum, key->maximum) > 0) + { + g_set_error (error, G_MARKUP_ERROR, + G_MARKUP_ERROR_INVALID_CONTENT, + "<range> specified minimum is greater than maxmimum"); + return FALSE; + } + + return g_markup_reader_expect_end (reader, cancellable, error); +} + +static gboolean +key_parse_choice (GMarkupReader *reader, + GCancellable *cancellable, + Key *key, + GError **error) +{ + const gchar *value; + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRING, "value", &value, + G_MARKUP_COLLECT_INVALID)) + return FALSE; + + if (strinfo_builder_contains (key->strinfo, value)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<choice value='%s'/> already given", value); + return FALSE; + } + + strinfo_builder_append_item (key->strinfo, value, 0); + key->has_choices = TRUE; + + return g_markup_reader_expect_end (reader, cancellable, error); +} + +static gboolean +key_parse_choices (GMarkupReader *reader, + GCancellable *cancellable, + Key *key, + GError **error) +{ + if (!g_markup_reader_collect_attributes (reader, error, G_MARKUP_COLLECT_INVALID, NULL)) + return FALSE; + + if (!key->type_string || (!g_str_equal (key->type_string, "s") && + !g_str_equal (key->type_string, "as") && + !g_str_equal (key->type_string, "ms"))) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<choices> only allowed for keys with type 's', 'as' or 'ms'"); + return FALSE; + } + + key->strinfo = g_string_new (NULL); + + if (!g_markup_reader_collect_elements (reader, cancellable, key, error, "choice", key_parse_choice, NULL)) + return FALSE; + + if (!key->has_choices) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<choices> must contain at least one <choice>"); + return FALSE; + } + + return TRUE; +} + +static gboolean +key_parse_aliases (GMarkupReader *reader, + GCancellable *cancellable, + Key *key, + GError **error) +{ + return TRUE; +} + +static gboolean +ignore_text (GMarkupReader *reader, + GCancellable *cancellable, + gpointer user_data, + GError **error) +{ + while (g_markup_reader_advance (reader, cancellable, error) && g_markup_reader_is_text (reader)) + ; + + if (g_markup_reader_is_end_element (reader)) + return TRUE; + + g_markup_reader_unexpected (reader, error); + + return FALSE; +} + +static gboolean +key_parse (GMarkupReader *reader, + GCancellable *cancellable, + Schema *schema, + GError **error) +{ + Key *key; + + g_assert (g_markup_reader_is_start_element (reader, "key")); + + key = g_slice_new0 (Key); + key->schema = schema; + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRDUP, + "name", &key->name, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "type", &key->type_string, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "enum", &key->enum_name, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "flags", &key->flags_name, + G_MARKUP_COLLECT_INVALID)) + goto error; + + if ((key->type_string != NULL) + (key->enum_name != NULL) + (key->flags_name != NULL) != 1) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_MISSING_ATTRIBUTE, + _("exactly one of 'type', 'enum' or 'flags' must be specified as an attribute to <key>")); + return FALSE; + } + + if (key->type_string) + { + if (!g_variant_type_string_is_valid (key->type_string)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + _("invalid GVariant type string '%s'"), key->type_string); + return FALSE; + } + + key->type = g_variant_type_new (key->type_string); + } + else if (key->enum_name) + key->type = g_variant_type_copy (G_VARIANT_TYPE_STRING); + else /* flags */ + key->type = g_variant_type_copy (G_VARIANT_TYPE_STRING_ARRAY); + + if (!g_markup_reader_collect_elements (reader, cancellable, key, error, + "summary", ignore_text, + "description", ignore_text, + "default", key_parse_default, + "range", key_parse_range, + "choices", key_parse_choices, + "aliases", key_parse_aliases, + NULL)) + goto error; + + if (!schema_add_key (schema, reader, key->name, key, error)) + goto error; + + return TRUE; + +error: + return FALSE; +} + +/* <schema> {{{1 */ + +struct _Override +{ + Schema *schema; + gchar *name; + + gchar *text; + gchar *context; + gchar *l10n; +}; + +static void +override_free (gpointer data) +{ + Override *override = data; + + g_free (override->name); + g_free (override->text); + g_free (override->context); + g_free (override->l10n); + + g_slice_free (Override, override); +} + +struct _Schema +{ + Dir *dir; + gchar *id; + + gboolean has_translated; + gboolean resolved; + + gchar *gettext_domain; + gchar *path; + + const gchar *extends_name; + Schema *extends; + const gchar *list_of_name; + Schema *list_of; + + GHashTable *children_names; + GHashTable *children; + GHashTable *keys; + GHashTable *overrides; +}; + +static void +schema_free (gpointer data) +{ + Schema *schema = data; + + g_slice_free (Schema, schema); +} + +static gboolean +schema_resolve (Schema *schema, + GError **error) +{ + static GSList *now_resolving; /* we don't unwind this properly in case of error */ + GSList me = { schema }; + + if (schema->resolved) + return TRUE; + + if (g_slist_find (now_resolving, schema)) + { + GSList *node; + GString *str; + + /* We have a reference cycle. */ + str = g_string_new ("Reference cycle detected: '%s'"); + for (node = now_resolving; node; node = node->next) + { + Schema *s = node->data; + + g_string_append_printf (str, "<- '%s'", s->id); + + /* Stop once we get back to ourselves. */ + if (s == schema) + break; + } + + g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, str->str); + g_string_free (str, TRUE); + return FALSE; + } + + me.next = now_resolving; + now_resolving = &me; + + if (schema->extends_name) + { + schema->extends = dir_resolve_schema (schema->dir, schema->extends_name, + "extends", "reference", schema->id, + error); + if (!schema->extends) + return FALSE; + } + + if (schema->list_of_name) + { + schema->list_of = dir_resolve_schema (schema->dir, schema->list_of_name, + "list-of", "reference", schema->id, + error); + if (!schema->list_of) + return FALSE; + } + + if (schema->children_names) + { + GHashTableIter iter; + gpointer name, id; + + schema->children = g_hash_table_new (g_str_hash, g_str_equal); + + g_hash_table_iter_init (&iter, schema->children_names); + while (g_hash_table_iter_next (&iter, &name, &id)) + { + Schema *child; + + child = dir_resolve_schema (schema->dir, id, + name, "child", schema->id, + error); + if (!child) + return FALSE; + + g_hash_table_insert (schema->children, name, child); + } + } + + if (schema->keys) + { + GHashTableIter iter; + gpointer key; + + g_hash_table_iter_init (&iter, schema->keys); + while (g_hash_table_iter_next (&iter, NULL, &key)) + if (!key_resolve (key, error)) + return FALSE; + } + + now_resolving = me.next; + + return TRUE; +} + +static Enum * +schema_resolve_enum (Schema *schema, + const gchar *id, + gboolean is_flags, + const gchar *for_key, + GError **error) +{ + return dir_resolve_enum (schema->dir, id, is_flags, for_key, schema->id, error); +} + +static gboolean +schema_add_key (Schema *schema, + GMarkupReader *reader, + const gchar *name, + Key *key, + GError **error) +{ + if (g_hash_table_contains (schema->keys, name)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<key name='%s'/> already defined in <schema id='%s'/>", name, schema->id); + return FALSE; + } + + g_hash_table_insert (schema->keys, g_strdup (name), key); + + return TRUE; +} + +static gboolean +schema_parse_child (GMarkupReader *reader, + GCancellable *cancellable, + Schema *schema, + GError **error) +{ + const gchar *name, *schema_id; + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRING, "name", &name, + G_MARKUP_COLLECT_STRING, "schema", &schema_id, + G_MARKUP_COLLECT_INVALID)) + return FALSE; + + if (g_hash_table_contains (schema->children_names, name)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<child name='%s'/> appears twice in <schema id='%s'/>", + name, schema->id); + return FALSE; + } + + g_hash_table_insert (schema->children_names, g_strdup (name), g_strdup (schema_id)); + + if (!g_markup_reader_expect_end (reader, cancellable, error)) + return FALSE; + + return TRUE; +} + +static gboolean +schema_parse_override (GMarkupReader *reader, + GCancellable *cancellable, + Schema *schema, + GError **error) +{ + Override *override; + + override = g_slice_new0 (Override); + override->schema = schema; + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRING, "name", &override->name, + G_MARKUP_COLLECT_STRING, "l10n", &override->l10n, + G_MARKUP_COLLECT_STRING, "context", &override->context, + G_MARKUP_COLLECT_INVALID)) + goto error; + + if (schema->overrides == NULL) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<override name='%s'/> appears within <schema id='%s'/> that is not extending another", + override->name, schema->id); + goto error; + } + + if (g_hash_table_contains (schema->overrides, override->name)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<override name='%s'/> appears twice in <schema id='%s'/>", override->name, schema->id); + return FALSE; + } + + override->text = g_markup_reader_collect_text (reader, cancellable, error); + if (!override->text) + goto error; + + g_hash_table_insert (schema->overrides, g_strdup (override->name), override); + + return TRUE; + +error: + override_free (override); + return FALSE; +} + +static gboolean +schema_parse (GMarkupReader *reader, + GCancellable *cancellable, + Dir *dir, + GError **error) +{ + Schema *schema; + + g_assert (g_markup_reader_is_start_element (reader, "schema")); + + schema = g_slice_new0 (Schema); + schema->dir = dir; + schema->keys = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, key_free); + schema->children_names = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRDUP, + "id", &schema->id, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "path", &schema->path, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "gettext-domain", &schema->gettext_domain, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "extends", &schema->extends_name, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "list-of", &schema->list_of_name, + G_MARKUP_COLLECT_INVALID)) + goto error; + + if (schema->extends_name) + schema->overrides = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, override_free); + + if (!g_markup_reader_collect_elements (reader, cancellable, schema, error, + "key", key_parse, + "child", schema_parse_child, + "override", schema_parse_override, + NULL)) + goto error; + + if (!dir_add_schema (dir, reader, schema->id, schema, error)) + goto error; + + return TRUE; + +error: + return FALSE; +} + +/* Directory handling {{{1 */ + +struct _Dir +{ + Dir *parent_dir; + const gchar *path; + GHashTable *excludes; + gboolean parsed; + + /* temporarily set while parsing <schemalist> */ + gchar *gettext_domain; + + GHashTable *schemas; + GHashTable *enums; +}; + +static gboolean +dir_add_enum (Dir *dir, + GMarkupReader *reader, + const gchar *id, + Enum *enum_, + GError **error) +{ + if (g_hash_table_contains (dir->enums, id)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<enum id='%s'/> or <flags id='%s'/> already defined in directory %s", + id, id, dir->path); + return FALSE; + } + + g_hash_table_insert (dir->enums, g_strdup (id), enum_); + + return TRUE; +} + +static gboolean +dir_add_schema (Dir *dir, + GMarkupReader *reader, + const gchar *id, + Schema *schema, + GError **error) +{ + if (g_hash_table_contains (dir->schemas, id)) + { + g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT, + "<schema id='%s'/> already defined in directory %s", id, dir->path); + return FALSE; + } + + g_hash_table_insert (dir->schemas, g_strdup (id), schema); + + return TRUE; +} + +static gboolean +dir_parse_schemalist (GMarkupReader *reader, + GCancellable *cancellable, + Dir *dir, + GError **error) +{ + gboolean ok; + + if (!g_markup_reader_collect_attributes (reader, error, + G_MARKUP_COLLECT_STRDUP | G_MARKUP_COLLECT_OPTIONAL, + "gettext-domain", &dir->gettext_domain, + G_MARKUP_COLLECT_INVALID)) + return FALSE; + + ok = g_markup_reader_collect_elements (reader, cancellable, dir, error, + "schema", schema_parse, "enum", enum_parse, "flags", enum_parse, NULL); + + g_free (dir->gettext_domain); + dir->gettext_domain = NULL; + + return ok; +} + +static gboolean +dir_parse_one_file (Dir *dir, + const gchar *filename, + GCancellable *cancellable, + GError **error) +{ + GFileInputStream *stream; + GMarkupReader *reader; + GFile *file; + gboolean ok; + + file = g_file_new_for_path (filename); + stream = g_file_read (file, cancellable, error); + g_object_unref (file); + + if (!stream) + return FALSE; + + reader = g_markup_reader_new (G_INPUT_STREAM (stream), + G_MARKUP_PREFIX_ERROR_POSITION | + G_MARKUP_TREAT_CDATA_AS_TEXT | + G_MARKUP_IGNORE_QUALIFIED | + G_MARKUP_IGNORE_PASSTHROUGH); + g_object_unref (stream); + + ok = g_markup_reader_collect_elements (reader, cancellable, dir, error, "schemalist", dir_parse_schemalist, NULL); + + g_object_unref (reader); + + if (!ok) + g_prefix_error (error, "%s: ", filename); + + return ok; +} + +static Dir *compile_dir; + +static gboolean +dir_parse (Dir *dir, + GCancellable *cancellable, + GError **error) +{ + GError *local_error = NULL; + gboolean ok = TRUE; + const gchar *name; + GDir *dirp; + + g_assert (!dir->parsed); + + dirp = g_dir_open (dir->path, 0, &local_error); + + dir->schemas = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, schema_free); + dir->enums = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, enum_free); + + if (!dirp) + { + /* If we get a G_FILE_ERROR_NOENT then ignore it, unless it was + * for the toplevel directory (ie: the one we were asked to + * compile). + */ + if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT) && dir != compile_dir) + { + g_error_free (local_error); + return TRUE; + } + + g_propagate_error (error, local_error); + return FALSE; + } + + while (ok && (name = g_dir_read_name (dirp))) + { + gchar *fullname; + + if (!g_str_has_suffix (name, ".xml")) + continue; + + if (dir->excludes && g_hash_table_contains (dir->excludes, name)) + continue; + + fullname = g_build_filename (dir->path, name, NULL); + ok = dir_parse_one_file (dir, fullname, cancellable, error); + g_free (fullname); + } + + g_dir_close (dirp); + + dir->parsed = ok; + + return ok; +} + +static Dir * +dir_new (const gchar *path, + Dir *parent_dir) +{ + Dir *dir; + + dir = g_slice_new0 (Dir); + dir->path = path; + dir->parent_dir = parent_dir; + + return dir; +} + +static void +setup_compile_dir (const gchar *directory, + const gchar * const *excluded) +{ + const gchar * const * system_dirs; + gint i, n; + + /* If we are compiling a system directory we want to include all of + * the directories that come before it. + * + * If we are compiling a non-system directory then we want to include + * all of the system directories before it. + * + * For example, of XDG_DATA_DIRS=/a:/b:/c then: + * + * - for building /a, we want a path of /a, /b, /c + * + * - for building /b, we want a path of /b, /c + * + * - for building /c, we want a path of /c + * + * - for building /x we want a path of /x, /a, /b, /c + */ + system_dirs = g_get_system_data_dirs (); + n = g_strv_length ((gchar **) system_dirs); + + /* We're building a linked list -- start at the end */ + for (i = n - 1; i >= 0; i--) + { + /* If we see our own directory then stop -- we don't want any more + * system dirs. We will deal with our directory below. + */ + if (g_str_equal (system_dirs[i], directory)) + break; + + compile_dir = dir_new (system_dirs[i], compile_dir); + } + + compile_dir = dir_new (directory, compile_dir); +} + +Schema * +dir_resolve_schema (Dir *dir, + const gchar *id, + const gchar *detail, + const gchar *purpose, + const gchar *caller, + GError **error) +{ + Schema *schema = NULL; + Dir *d; + + g_assert (dir); + + for (d = dir; d; d = d->parent_dir) + { + if (!dir->parsed) + if (!dir_parse (dir, NULL, error)) + return FALSE; + + schema = g_hash_table_lookup (dir->schemas, id); + + if (schema) + break; + } + + if (!schema) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Unable to locate schema '%s' needed for '%s' %s of %s", + id, detail, purpose, caller); + return NULL; + } + + if (!schema_resolve (schema, error)) + return NULL; + + return schema; +} + +Enum * +dir_resolve_enum (Dir *dir, + const gchar *id, + gboolean is_flags, + const gchar *for_key, + const gchar *of_schema, + GError **error) +{ + Enum *enum_ = NULL; + Dir *d; + + g_assert (dir); + + for (d = dir; d; d = d->parent_dir) + { + if (!dir->parsed) + if (!dir_parse (dir, NULL, error)) + return FALSE; + + enum_ = g_hash_table_lookup (dir->enums, id); + + if (enum_) + break; + } + + if (!enum_ || enum_->is_flags != is_flags) + { + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED, + "Unable to locate <%s id='%s'/> needed for key '%s' of schema '%s'", + is_flags ? "flags" : "enum", id, for_key, of_schema); + return NULL; + } + + return enum_; +} + +static gboolean +dir_resolve (Dir *dir, + GError **error) +{ + GHashTableIter iter; + gpointer schema; + + if (!dir_parse (dir, NULL, error)) + return FALSE; + + g_hash_table_iter_init (&iter, dir->schemas); + while (g_hash_table_iter_next (&iter, NULL, &schema)) + if (!schema_resolve (schema, error)) + return FALSE; + + return TRUE; +} + +static GVariant * +key_compile (Key *key, + GError **error) +{ + GVariantBuilder builder; + + g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE); + + return g_variant_builder_end (&builder); +} + +static GHashTable * +schema_compile (Schema *schema, + GError **error) +{ + GHashTable *compiled_schema; + GvdbItem *root_item; + GHashTableIter iter; + gpointer key, name; + gpointer id; + + compiled_schema = gvdb_hash_table_new (NULL, NULL); + root_item = gvdb_hash_table_insert (compiled_schema, ""); + + if (schema->path) + gvdb_hash_table_insert_string (compiled_schema, ".path", schema->path); + + if (schema->list_of) + gvdb_hash_table_insert_string (compiled_schema, ".list-of", schema->list_of_name); + + if (schema->extends) + gvdb_hash_table_insert_string (compiled_schema, ".extends", schema->extends_name); + + /* Only store the gettext domain if a key was actually translated */ + if (schema->has_translated) + gvdb_hash_table_insert_string (compiled_schema, ".gettext-domain", schema->gettext_domain); + + g_hash_table_iter_init (&iter, schema->keys); + while (g_hash_table_iter_next (&iter, &name, &key)) + { + GVariant *compiled_key; + GvdbItem *key_item; + + compiled_key = key_compile (key, error); + if (!compiled_key) + goto error; + + key_item = gvdb_hash_table_insert (compiled_schema, name); + gvdb_item_set_parent (key_item, root_item); + gvdb_item_set_value (key_item, compiled_key); + } + + g_hash_table_iter_init (&iter, schema->children); + while (g_hash_table_iter_next (&iter, &name, &id)) + { + GvdbItem *child_item; + + child_item = gvdb_hash_table_insert (compiled_schema, name); + gvdb_item_set_parent (child_item, root_item); + gvdb_item_set_value (child_item, g_variant_new_string (id)); + } + + return compiled_schema; +error: + g_assert_not_reached (); +} + +static GHashTable * +dir_compile (Dir *dir, + GError **error) +{ + GHashTable *compiled_dir; + GvdbItem *root_item; + GHashTableIter iter; + gpointer schema, id; + + compiled_dir = gvdb_hash_table_new (NULL, NULL); + root_item = gvdb_hash_table_insert (compiled_dir, ""); + + g_hash_table_iter_init (&iter, dir->schemas); + while (g_hash_table_iter_next (&iter, &id, &schema)) + { + GHashTable *compiled_schema; + GvdbItem *schema_item; + + compiled_schema = schema_compile (schema, error); + if (!compiled_schema) + goto error; + + schema_item = gvdb_hash_table_insert (compiled_dir, id); + gvdb_item_set_parent (schema_item, root_item); + gvdb_item_set_hash_table (schema_item, compiled_schema); + } + + return compiled_dir; +error: + g_assert_not_reached (); +} + +int +main (void) +{ + GError *error = NULL; + setup_compile_dir ("/home/desrt/.cache/jhbuild/install/share/glib-2.0/schemas", NULL); + if (!dir_resolve (compile_dir, &error)) + g_printerr ("%s\n", error->message); + return error ? 1 : 0; +} + +/* Epilogue {{{1 */ + +/* vim:set foldmethod=marker: */ + |