medit/moo/mooedit/gtksourceview/upstream/gtksourcelanguage-parser-2.c

1769 lines
45 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; coding: utf-8 -*-
* gtksourcelanguage-parser-2.c
* Language specification parser for 2.0 version .lang files
*
* Copyright (C) 2003 - Gustavo Giráldez <gustavo.giraldez@gmx.net>
* Copyright (C) 2005, 2006 - Emanuele Aina, Marco Barisione
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#undef ENABLE_DEBUG
#ifdef ENABLE_DEBUG
#define DEBUG(x) x
#else
#define DEBUG(x)
#endif
#include <string.h>
#include <fcntl.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef G_OS_WIN32
#include <io.h>
#endif
#include <libxml/xmlreader.h>
#include <glib/gstdio.h>
#include "gtksourceview-i18n.h"
#include "gtksourcebuffer.h"
#include "gtksourcelanguage.h"
#include "gtksourcelanguage-private.h"
#include "gtksourcecontextengine.h"
#include <glib/gregex.h>
#define PARSER_ERROR (parser_error_quark ())
#define ATTR_NO_STYLE ""
typedef enum {
PARSER_ERROR_CANNOT_OPEN = 0,
PARSER_ERROR_CANNOT_VALIDATE,
PARSER_ERROR_INVALID_DOC,
PARSER_ERROR_WRONG_VERSION,
PARSER_ERROR_WRONG_ID,
PARSER_ERROR_WRONG_STYLE,
PARSER_ERROR_MALFORMED_REGEX,
PARSER_ERROR_MALFORMED_MAP_TO
} ParserError;
struct _ParserState
{
/* The args passed to _file_parse_version2() */
xmlTextReader *reader;
char *filename;
GtkSourceLanguage *language;
GtkSourceContextData *ctx_data;
gchar *language_decoration;
/* A stack of id that representing parent contexts */
GQueue *curr_parents;
/* The id of the current language (used to decorate ids) */
gchar *current_lang_id;
/* An hash table with the defined regex as strings used to
* resolve references (the key is the id) */
GHashTable *defined_regexes;
/* Maps style ids to GtkSourceStyleInfo objects.
* Contains all the styles defined in the lang files parsed
* while parsing the main language file. For example, if the main
* language is C, also styles in def.lang, gtk-doc.lang, etc. are
* stored in this hash table. For styles defined in language files
* different from the main one, the name is _not_ stored, since it
* is not used in other places of the code. So, if the main language is
* C, only the name of styles defined in c.lang is stored, while for
* the styles defined in def.lang, etc. the name is not stored. */
GHashTable *styles_mapping;
/* The list of loaded languages (the item are xmlChar pointers),
* mapping is id -> id */
GHashTable *loaded_lang_ids;
/* The list of replacements. The queue object is owned by the caller,
* so parser_state only adds stuff to it */
GQueue *replacements;
/* A serial number incremented to get unique generated names */
guint id_cookie;
/* The default flags used by the regexes */
GRegexCompileFlags regex_compile_flags;
gchar *opening_delimiter;
gchar *closing_delimiter;
GError *error;
};
typedef struct _ParserState ParserState;
static GQuark parser_error_quark (void);
static gboolean str_to_bool (const xmlChar *string);
static gchar *generate_new_id (ParserState *parser_state);
static gboolean id_is_decorated (const gchar *id,
gchar **lang_id);
static gchar *decorate_id (ParserState *parser_state,
const gchar *id);
static ParserState *parser_state_new (GtkSourceLanguage *language,
GtkSourceContextData *ctx_data,
GHashTable *defined_regexes,
GHashTable *styles_mapping,
GQueue *replacements,
xmlTextReader *reader,
const char *filename,
GHashTable *loaded_lang_ids);
static void parser_state_destroy (ParserState *parser_state);
static gboolean file_parse (gchar *filename,
GtkSourceLanguage *language,
GtkSourceContextData *ctx_data,
GHashTable *defined_regexes,
GHashTable *styles,
GHashTable *loaded_lang_ids,
GQueue *replacements,
GError **error);
static GRegexCompileFlags
update_regex_flags (GRegexCompileFlags flags,
const xmlChar *option_name,
const xmlChar *bool_value);
static gboolean create_definition (ParserState *parser_state,
gchar *id,
gchar *parent_id,
gchar *style,
GError **error);
static void handle_context_element (ParserState *parser_state);
static void handle_language_element (ParserState *parser_state);
static void handle_define_regex_element (ParserState *parser_state);
static void handle_default_regex_options_element
(ParserState *parser_state);
static void handle_replace_element (ParserState *parser_state);
static void element_start (ParserState *parser_state);
static void element_end (ParserState *parser_state);
static gboolean replace_by_id (const GMatchInfo *match_info,
GString *expanded_regex,
gpointer data);
static gchar *expand_regex (ParserState *parser_state,
gchar *regex,
GRegexCompileFlags flags,
gboolean do_expand_vars,
gboolean insert_parentheses,
GError **error);
static GQuark
parser_error_quark (void)
{
static GQuark err_q = 0;
if (err_q == 0)
err_q = g_quark_from_static_string (
"parser-error-quark");
return err_q;
}
static gboolean
str_to_bool (const xmlChar *string)
{
g_return_val_if_fail (string != NULL, FALSE);
return g_ascii_strcasecmp ("true", (const gchar *) string) == 0;
}
static gchar *
generate_new_id (ParserState *parser_state)
{
gchar *id;
id = g_strdup_printf ("unnamed-%u", parser_state->id_cookie);
parser_state->id_cookie++;
DEBUG (g_message ("generated id %s", id));
return id;
}
static gboolean
id_is_decorated (const gchar *id,
gchar **lang_id)
{
/* This function is quite simple because the XML validator check for
* the correctness of the id with a regex */
const gchar *colon;
gboolean is_decorated = FALSE;
colon = strchr (id, ':');
if (colon != NULL && strcmp ("*", colon + 1) != 0)
{
is_decorated = TRUE;
if (lang_id != NULL)
*lang_id = g_strndup (id, colon - id);
}
return is_decorated;
}
static gchar *
decorate_id (ParserState *parser_state,
const gchar *id)
{
gchar *decorated_id;
decorated_id = g_strdup_printf ("%s:%s",
parser_state->current_lang_id, id);
DEBUG (g_message ("decorated '%s' to '%s'", id, decorated_id));
return decorated_id;
}
static gboolean
lang_id_is_already_loaded (ParserState *parser_state, gchar *lang_id)
{
return g_hash_table_lookup (parser_state->loaded_lang_ids, lang_id) != NULL;
}
static GRegexCompileFlags
get_regex_flags (xmlNode *node,
GRegexCompileFlags flags)
{
xmlAttr *attribute;
for (attribute = node->properties; attribute != NULL; attribute = attribute->next)
{
g_return_val_if_fail (attribute->children != NULL, flags);
flags = update_regex_flags (flags, attribute->name,
attribute->children->content);
}
return flags;
}
static GtkSourceContextFlags
get_context_flags (ParserState *parser_state)
{
guint i;
xmlChar *value;
GtkSourceContextFlags flags = GTK_SOURCE_CONTEXT_EXTEND_PARENT;
const gchar *names[] = {
"extend-parent", "end-parent", "end-at-line-end",
"first-line-only", "once-only", "style-inside"
};
GtkSourceContextFlags values[] = {
GTK_SOURCE_CONTEXT_EXTEND_PARENT,
GTK_SOURCE_CONTEXT_END_PARENT,
GTK_SOURCE_CONTEXT_END_AT_LINE_END,
GTK_SOURCE_CONTEXT_FIRST_LINE_ONLY,
GTK_SOURCE_CONTEXT_ONCE_ONLY,
GTK_SOURCE_CONTEXT_STYLE_INSIDE
};
g_assert (G_N_ELEMENTS (names) == G_N_ELEMENTS (values));
for (i = 0; i < G_N_ELEMENTS (names); ++i)
{
value = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST names[i]);
if (value != NULL)
{
if (str_to_bool (value))
flags |= values[i];
else
flags &= ~values[i];
}
xmlFree (value);
}
return flags;
}
static gboolean
create_definition (ParserState *parser_state,
gchar *id,
gchar *parent_id,
gchar *style,
GError **error)
{
gchar *match = NULL, *start = NULL, *end = NULL;
gchar *prefix = NULL, *suffix = NULL;
GtkSourceContextFlags flags;
xmlNode *context_node, *child;
GString *all_items = NULL;
GRegexCompileFlags match_flags = 0, start_flags = 0, end_flags = 0;
GError *tmp_error = NULL;
g_assert (parser_state->ctx_data != NULL);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
flags = get_context_flags (parser_state);
DEBUG (g_message ("creating context %s, child of %s", id, parent_id ? parent_id : "(null)"));
/* Fetch the content of the sublements using the tree API on
* the current node */
context_node = xmlTextReaderExpand (parser_state->reader);
/* The file should be validated so this should not happen */
g_assert (context_node != NULL);
for (child = context_node->children; child != NULL; child = child->next)
{
if (child->type != XML_READER_TYPE_ELEMENT)
continue;
/* FIXME: add PCRE_EXTRA support in EggRegex
* Huh? */
g_assert (child->name);
if (xmlStrcmp (BAD_CAST "match", child->name) == 0
&& child->children != NULL)
{
/* <match> */
match = g_strdup ((gchar *)child->children->content);
match_flags = get_regex_flags (child, parser_state->regex_compile_flags);
}
else if (xmlStrcmp (BAD_CAST "start", child->name) == 0)
{
/* <start> */
if (child->children != NULL)
{
start = g_strdup ((gchar *)child->children->content);
}
else
{
/* If the <start> element is present but
* has no content use an empty string */
start = g_strdup ("");
}
start_flags = get_regex_flags (child, parser_state->regex_compile_flags);
}
else if (xmlStrcmp (BAD_CAST "end", child->name) == 0)
{
/* <end> */
if (child->children != NULL)
end = g_strdup ((gchar *)child->children->content);
else
{
/* If the <end> element is present but
* has no content use an empty string */
end = g_strdup ("");
}
end_flags = get_regex_flags (child, parser_state->regex_compile_flags);
}
else if (xmlStrcmp (BAD_CAST "prefix", child->name) == 0)
{
/* <prefix> */
if (child->children != NULL)
prefix = g_strdup ((gchar*) child->children->content);
else
prefix = g_strdup ("");
}
else if (xmlStrcmp (BAD_CAST "suffix", child->name) == 0)
{
/* <suffix> */
if (child->children != NULL)
suffix = g_strdup ((gchar*) child->children->content);
else
suffix = g_strdup ("");
}
else if (xmlStrcmp (BAD_CAST "keyword", child->name) == 0 &&
child->children != NULL)
{
/* FIXME: how to specify regex options for keywords?
* They can be specified in prefix, so it's not really
* important, but would be nice (case-sensitive). */
/* <keyword> */
if (all_items == NULL)
{
all_items = g_string_new (NULL);
if (prefix != NULL)
g_string_append (all_items, prefix);
else
g_string_append (all_items,
parser_state->opening_delimiter);
g_string_append (all_items, "(");
g_string_append (all_items, (gchar*) child->children->content);
}
else
{
g_string_append (all_items, "|");
g_string_append (all_items, (gchar*) child->children->content);
}
}
}
if (all_items != NULL)
{
g_string_append (all_items, ")");
if (suffix != NULL)
g_string_append (all_items, suffix);
else
g_string_append (all_items,
parser_state->closing_delimiter);
match = g_string_free (all_items, FALSE);
match_flags = parser_state->regex_compile_flags;
}
DEBUG (g_message ("start: '%s'", start ? start : "(null)"));
DEBUG (g_message ("end: '%s'", end ? end : "(null)"));
DEBUG (g_message ("match: '%s'", match ? match : "(null)"));
if (tmp_error == NULL && start != NULL)
{
gchar *tmp = start;
start = expand_regex (parser_state, start, start_flags,
TRUE, FALSE, &tmp_error);
g_free (tmp);
}
if (tmp_error == NULL && end != NULL)
{
gchar *tmp = end;
end = expand_regex (parser_state, end, end_flags,
TRUE, FALSE, &tmp_error);
g_free (tmp);
}
if (tmp_error == NULL && match != NULL)
{
gchar *tmp = match;
match = expand_regex (parser_state, match, match_flags,
TRUE, FALSE, &tmp_error);
g_free (tmp);
}
if (tmp_error == NULL)
_gtk_source_context_data_define_context (parser_state->ctx_data,
id,
parent_id,
match,
start,
end,
style,
flags,
&tmp_error);
g_free (match);
g_free (start);
g_free (end);
g_free (prefix);
g_free (suffix);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
static gboolean
add_ref (ParserState *parser_state,
const gchar *ref,
GtkSourceContextRefOptions options,
const gchar *style,
GError **error)
{
gboolean all = FALSE;
gchar *ref_id;
gchar *lang_id = NULL;
GError *tmp_error = NULL;
/* Return if an error is already set */
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (id_is_decorated (ref, &lang_id))
{
if (!lang_id_is_already_loaded (parser_state, lang_id))
{
GtkSourceLanguageManager *lm;
GtkSourceLanguage *imported_language;
lm = _gtk_source_language_get_language_manager (parser_state->language);
imported_language = gtk_source_language_manager_get_language (lm, lang_id);
if (imported_language == NULL)
{
g_set_error (&tmp_error,
PARSER_ERROR,
PARSER_ERROR_WRONG_ID,
"unable to resolve language '%s' in ref '%s'",
lang_id, ref);
}
else
{
file_parse (imported_language->priv->lang_file_name,
parser_state->language,
parser_state->ctx_data,
parser_state->defined_regexes,
parser_state->styles_mapping,
parser_state->loaded_lang_ids,
parser_state->replacements,
&tmp_error);
if (tmp_error != NULL)
{
GError *tmp_error2 = NULL;
g_set_error (&tmp_error2, PARSER_ERROR, tmp_error->code,
"In file '%s' referenced from '%s': %s",
imported_language->priv->lang_file_name,
parser_state->language->priv->lang_file_name,
tmp_error->message);
g_clear_error (&tmp_error);
tmp_error = tmp_error2;
}
}
}
ref_id = g_strdup (ref);
}
else
{
ref_id = decorate_id (parser_state, ref);
}
if (tmp_error == NULL && parser_state->ctx_data != NULL)
{
if (g_str_has_suffix (ref, ":*"))
{
all = TRUE;
ref_id [strlen (ref_id) - 2] = '\0';
}
if (all && (options & (GTK_SOURCE_CONTEXT_IGNORE_STYLE | GTK_SOURCE_CONTEXT_OVERRIDE_STYLE)))
{
g_set_error (&tmp_error, PARSER_ERROR,
PARSER_ERROR_WRONG_STYLE,
"style override used with wildcard context reference"
" in language '%s' in ref '%s'",
lang_id != NULL ? lang_id : parser_state->current_lang_id, ref);
}
}
if (tmp_error == NULL && parser_state->ctx_data != NULL)
{
gchar *container_id;
container_id = g_queue_peek_head (parser_state->curr_parents);
/* If the document is validated container_id is never NULL */
g_assert (container_id);
_gtk_source_context_data_add_ref (parser_state->ctx_data,
container_id,
ref_id,
options,
style,
all,
&tmp_error);
DEBUG (g_message ("appended %s in %s", ref_id, container_id));
}
g_free (lang_id);
g_free (ref_id);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
static gboolean
create_sub_pattern (ParserState *parser_state,
gchar *id,
gchar *sub_pattern,
gchar *style,
GError **error)
{
gchar *container_id;
xmlChar *where;
GError *tmp_error = NULL;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
container_id = g_queue_peek_head (parser_state->curr_parents);
/* If the document is validated container is never NULL */
g_assert (container_id);
where = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "where");
_gtk_source_context_data_add_sub_pattern (parser_state->ctx_data,
id,
container_id,
sub_pattern,
(gchar*) where,
style,
&tmp_error);
xmlFree (where);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
static void
handle_context_element (ParserState *parser_state)
{
gchar *id, *parent_id, *style_ref;
xmlChar *ref, *sub_pattern, *tmp;
int is_empty;
gboolean success;
gboolean ignore_style = FALSE;
GtkSourceContextRefOptions options = 0;
GError *tmp_error = NULL;
g_return_if_fail (parser_state->error == NULL);
ref = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "ref");
sub_pattern = xmlTextReaderGetAttribute (parser_state->reader,
BAD_CAST "sub-pattern");
tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "ignore-style");
if (tmp != NULL && str_to_bool (tmp))
ignore_style = TRUE;
xmlFree (tmp);
tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "style-ref");
if (tmp == NULL || id_is_decorated ((gchar*) tmp, NULL))
style_ref = g_strdup ((gchar*) tmp);
else
style_ref = decorate_id (parser_state, (gchar*) tmp);
xmlFree (tmp);
if (ignore_style && ref == NULL)
{
g_set_error (&parser_state->error,
PARSER_ERROR,
PARSER_ERROR_WRONG_STYLE,
"ignore-style used not in a reference to context");
xmlFree (ref);
g_free (style_ref);
return;
}
if (ignore_style)
{
options |= GTK_SOURCE_CONTEXT_IGNORE_STYLE;
if (style_ref != NULL)
g_warning ("in file %s: style-ref and ignore-style used simultaneously",
parser_state->filename);
}
/* XXX */
if (!ignore_style && style_ref != NULL &&
g_hash_table_lookup (parser_state->styles_mapping, style_ref) == NULL)
{
g_warning ("in file %s: style '%s' not defined", parser_state->filename, style_ref);
}
if (ref != NULL)
{
tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "original");
if (tmp != NULL && str_to_bool (tmp))
options |= GTK_SOURCE_CONTEXT_REF_ORIGINAL;
xmlFree (tmp);
if (style_ref != NULL)
options |= GTK_SOURCE_CONTEXT_OVERRIDE_STYLE;
add_ref (parser_state, (gchar*) ref, options, style_ref, &tmp_error);
}
else
{
char *freeme = NULL;
tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "id");
if (tmp == NULL)
{
freeme = generate_new_id (parser_state);
tmp = xmlStrdup (BAD_CAST freeme);
}
if (id_is_decorated ((gchar*) tmp, NULL))
id = g_strdup ((gchar*) tmp);
else
id = decorate_id (parser_state, (gchar*) tmp);
g_free (freeme);
xmlFree (tmp);
if (parser_state->ctx_data != NULL)
{
if (sub_pattern != NULL)
{
create_sub_pattern (parser_state, id,
(gchar *)sub_pattern, style_ref,
&tmp_error);
}
else
{
parent_id = g_queue_peek_head (
parser_state->curr_parents);
is_empty = xmlTextReaderIsEmptyElement (
parser_state->reader);
if (is_empty)
success = _gtk_source_context_data_define_context (parser_state->ctx_data,
id,
parent_id,
"$^",
NULL,
NULL,
NULL,
0,
&tmp_error);
else
success = create_definition (parser_state, id, parent_id,
style_ref, &tmp_error);
if (success && !is_empty)
{
/* Push the new context in the curr_parents
* stack only if other contexts can be
* defined inside it */
g_queue_push_head (parser_state->curr_parents,
g_strdup (id));
}
}
}
g_free (id);
}
g_free (style_ref);
xmlFree (sub_pattern);
xmlFree (ref);
if (tmp_error != NULL)
g_propagate_error (&parser_state->error, tmp_error);
}
static void
handle_replace_element (ParserState *parser_state)
{
xmlChar *id, *ref;
GtkSourceContextReplace *repl;
gchar *replace_with;
id = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "id");
ref = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "ref");
if (id_is_decorated ((gchar*) ref, NULL))
replace_with = g_strdup ((gchar *) ref);
else
replace_with = decorate_id (parser_state, (gchar*) ref);
repl = _gtk_source_context_replace_new ((const gchar *) id, replace_with);
g_queue_push_tail (parser_state->replacements, repl);
g_free (replace_with);
xmlFree (ref);
xmlFree (id);
}
static void
handle_language_element (ParserState *parser_state)
{
/* FIXME: check that the language name, version, etc. are the
* right ones - Paolo */
xmlChar *lang_id, *lang_version;
xmlChar *expected_version = BAD_CAST "2.0";
g_return_if_fail (parser_state->error == NULL);
lang_version = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "version");
if (lang_version == NULL ||
xmlStrcmp (expected_version, lang_version) != 0)
{
g_set_error (&parser_state->error,
PARSER_ERROR,
PARSER_ERROR_WRONG_VERSION,
"wrong language version '%s', expected '%s'",
lang_version ? (gchar*) lang_version : "(none)",
(gchar*) expected_version);
}
else
{
lang_id = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "id");
parser_state->current_lang_id = g_strdup ((gchar *) lang_id);
g_hash_table_insert (parser_state->loaded_lang_ids, lang_id, lang_id);
}
xmlFree (lang_version);
}
struct ReplaceByIdData {
ParserState *parser_state;
GError *error;
};
static gboolean
replace_by_id (const GMatchInfo *match_info,
GString *expanded_regex,
gpointer user_data)
{
gchar *id, *subst, *escapes;
gchar *tmp;
GError *tmp_error = NULL;
struct ReplaceByIdData *data = user_data;
escapes = g_match_info_fetch (match_info, 1);
tmp = g_match_info_fetch (match_info, 2);
g_strstrip (tmp);
if (id_is_decorated (tmp, NULL))
id = g_strdup (tmp);
else
id = decorate_id (data->parser_state, tmp);
g_free (tmp);
subst = g_hash_table_lookup (data->parser_state->defined_regexes, id);
if (subst == NULL)
g_set_error (&tmp_error,
PARSER_ERROR, PARSER_ERROR_WRONG_ID,
_("Unknown id '%s' in regex '%s'"), id,
g_match_info_get_string (match_info));
if (tmp_error == NULL)
{
g_string_append (expanded_regex, escapes);
g_string_append (expanded_regex, subst);
}
g_free (escapes);
g_free (id);
if (tmp_error != NULL)
{
g_propagate_error (&data->error, tmp_error);
return TRUE;
}
return FALSE;
}
static GRegexCompileFlags
update_regex_flags (GRegexCompileFlags flags,
const xmlChar *option_name,
const xmlChar *value)
{
GRegexCompileFlags single_flag;
gboolean set_flag;
DEBUG (g_message ("setting the '%s' regex flag to %s", option_name, value));
set_flag = str_to_bool (value);
if (xmlStrcmp (BAD_CAST "case-sensitive", option_name) == 0)
{
single_flag = G_REGEX_CASELESS;
set_flag = !set_flag;
}
else if (xmlStrcmp (BAD_CAST "extended", option_name) == 0)
{
single_flag = G_REGEX_EXTENDED;
}
else if (xmlStrcmp (BAD_CAST "dupnames", option_name) == 0)
{
single_flag = G_REGEX_DUPNAMES;
}
else
{
return flags;
}
if (set_flag)
flags |= single_flag;
else
flags &= ~single_flag;
return flags;
}
static gchar *
expand_regex_vars (ParserState *parser_state, gchar *regex, gint len, GError **error)
{
/* This is the commented regex without the doubled escape needed
* in a C string:
*
* (?<!\\)(\\\\)*\\%\{([^@]*?)\}
* |------------||---||------||-|
* | | | |
* | the string | the ending
* | "\%{" | bracket "}"
* | |
* a even sequence |
* of escapes or the id of the
* a char that included regex
* is not an (not greedy)
* escape (this
* is not matched)
*
* The first block is needed to ensure that the sequence is
* not escaped.
* Matches with an id containing a "@" sign are ignored and
* passed to the engine because they are references to subpatterns
* in a different regex (i.e. in the start regex while we are in the
* end regex.)
* The sub pattern containing the id is the second.
*/
const gchar *re = "(?<!\\\\)(\\\\\\\\)*\\\\%\\{([^@]*?)\\}";
gchar *expanded_regex;
GRegex *egg_re;
struct ReplaceByIdData data;
if (regex == NULL)
return NULL;
egg_re = g_regex_new (re, G_REGEX_NEWLINE_LF, 0, NULL);
data.parser_state = parser_state;
data.error = NULL;
expanded_regex = g_regex_replace_eval (egg_re, regex, len, 0, 0,
replace_by_id, &data, NULL);
if (data.error == NULL)
{
DEBUG (g_message ("expanded regex vars '%s' to '%s'",
regex, expanded_regex));
}
g_regex_unref (egg_re);
if (data.error != NULL)
{
g_free (expanded_regex);
g_propagate_error (error, data.error);
return NULL;
}
return expanded_regex;
}
static gboolean
replace_delimiter (const GMatchInfo *match_info,
GString *expanded_regex,
gpointer data)
{
gchar *delim, *escapes;
ParserState *parser_state = data;
escapes = g_match_info_fetch (match_info, 1);
g_string_append (expanded_regex, escapes);
delim = g_match_info_fetch (match_info, 2);
DEBUG (g_message ("replacing '\\%%%s'", delim));
switch (delim[0])
{
case '[':
g_string_append (expanded_regex,
parser_state->opening_delimiter);
break;
case ']':
g_string_append (expanded_regex,
parser_state->closing_delimiter);
break;
}
g_free (delim);
g_free (escapes);
return FALSE;
}
static gchar *
expand_regex_delimiters (ParserState *parser_state,
gchar *regex,
gint len)
{
/* This is the commented regex without the doubled escape needed
* in a C string:
*
* (?<!\\)(\\\\)*\\%([\[|\])
* |------------||---------|
* | |
* | the strings
* | "\%[" or "\%]"
* |
* a even sequence of escapes or
* a char that is not an escape
* (this is not matched)
*
* The first block is needed to ensure that the sequence is
* not escaped.
*/
const gchar *re = "(?<!\\\\)(\\\\\\\\)*\\\\%(\\[|\\])";
gchar *expanded_regex;
static GRegex *egg_re;
if (regex == NULL)
return NULL;
if (egg_re == NULL)
egg_re = g_regex_new (re, G_REGEX_NEWLINE_LF | G_REGEX_OPTIMIZE, 0, NULL);
expanded_regex = g_regex_replace_eval (egg_re, regex, len, 0, 0,
replace_delimiter, parser_state, NULL);
DEBUG (g_message ("expanded regex delims '%s' to '%s'",
regex, expanded_regex));
return expanded_regex;
}
static gchar *
expand_regex (ParserState *parser_state,
gchar *regex,
GRegexCompileFlags flags,
gboolean do_expand_vars,
gboolean insert_parentheses,
GError **error)
{
gchar *tmp_regex;
GString *expanded_regex;
static GRegex *backref_re = NULL;
g_assert (parser_state != NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
if (regex == NULL)
return NULL;
if (backref_re == NULL)
backref_re = g_regex_new ("(?<!\\\\)(\\\\\\\\)*\\\\[0-9]",
G_REGEX_OPTIMIZE | G_REGEX_NEWLINE_LF,
0,
NULL);
if (g_regex_match (backref_re, regex, 0, NULL))
{
/* This may be a backreference, or it may be an octal character */
GRegex *compiled;
compiled = g_regex_new (regex, flags | G_REGEX_NEWLINE_LF, 0, error);
if (compiled == NULL)
return NULL;
if (g_regex_get_max_backref (compiled) > 0)
{
g_set_error (error, PARSER_ERROR, PARSER_ERROR_MALFORMED_REGEX,
_("in regex '%s': backreferences are not supported"),
regex);
g_regex_unref (compiled);
return NULL;
}
g_regex_unref (compiled);
}
if (do_expand_vars)
{
tmp_regex = expand_regex_vars (parser_state, regex, -1, error);
if (tmp_regex == NULL)
return NULL;
}
else
{
tmp_regex = g_strdup (regex);
}
regex = tmp_regex;
tmp_regex = expand_regex_delimiters (parser_state, regex, -1);
g_free (regex);
/* Set the options and add not capturing parentheses if
* insert_parentheses is TRUE (this is needed for included
* regular expressions.) */
expanded_regex = g_string_new ("");
if (insert_parentheses)
g_string_append (expanded_regex, "(?:");
g_string_append (expanded_regex, "(?");
if (flags != 0)
{
if (flags & G_REGEX_CASELESS)
g_string_append (expanded_regex, "i");
if (flags & G_REGEX_EXTENDED)
g_string_append (expanded_regex, "x");
/* J is added here if it's used, but -J isn't added
* below */
if (flags & G_REGEX_DUPNAMES)
g_string_append (expanded_regex, "J");
}
if ((flags & (G_REGEX_CASELESS | G_REGEX_EXTENDED)) != (G_REGEX_CASELESS | G_REGEX_EXTENDED))
{
g_string_append (expanded_regex, "-");
if (!(flags & G_REGEX_CASELESS))
g_string_append (expanded_regex, "i");
if (!(flags & G_REGEX_EXTENDED))
g_string_append (expanded_regex, "x");
}
g_string_append (expanded_regex, ")");
g_string_append (expanded_regex, tmp_regex);
if (insert_parentheses)
{
/* The '\n' is needed otherwise, if the regex is "extended"
* and it ends with a comment, the ')' is appended inside the
* comment itself */
if (flags & G_REGEX_EXTENDED)
g_string_append (expanded_regex, "\n");
/* FIXME: if the regex is not extended this doesn't works */
g_string_append (expanded_regex, ")");
}
g_free (tmp_regex);
return g_string_free (expanded_regex, FALSE);
}
static void
handle_define_regex_element (ParserState *parser_state)
{
gchar *id;
xmlChar *regex;
xmlChar *tmp;
gchar *expanded_regex;
int i;
const gchar *regex_options[] = {"extended", "case-sensitive", "dupnames", NULL};
GRegexCompileFlags flags;
GError *tmp_error = NULL;
int type;
g_return_if_fail (parser_state->error == NULL);
if (parser_state->ctx_data == NULL)
return;
tmp = xmlTextReaderGetAttribute (parser_state->reader, BAD_CAST "id");
/* If the file is validated <define-regex> must have an id
* attribute */
g_assert (tmp != NULL);
if (id_is_decorated ((gchar *)tmp, NULL))
id = g_strdup ((gchar *)tmp);
else
id = decorate_id (parser_state, (gchar *)tmp);
xmlFree (tmp);
flags = parser_state->regex_compile_flags;
for (i=0; regex_options[i] != NULL; i++)
{
tmp = xmlTextReaderGetAttribute (parser_state->reader,
BAD_CAST regex_options[i]);
if (tmp != NULL)
{
flags = update_regex_flags (flags,
BAD_CAST regex_options[i],
tmp);
}
xmlFree (tmp);
}
xmlTextReaderRead (parser_state->reader);
type = xmlTextReaderNodeType (parser_state->reader);
if (type == XML_READER_TYPE_TEXT || type == XML_READER_TYPE_CDATA)
regex = xmlTextReaderValue (parser_state->reader);
else
regex = xmlStrdup (BAD_CAST "");
expanded_regex = expand_regex (parser_state, (gchar*) regex, flags,
TRUE, TRUE, &tmp_error);
if (tmp_error == NULL)
{
DEBUG (g_message ("defined regex %s: \"%s\"", id, (gchar *)regex));
g_hash_table_insert (parser_state->defined_regexes, id, expanded_regex);
}
xmlFree (regex);
if (tmp_error != NULL)
g_propagate_error (&parser_state->error, tmp_error);
}
static void
handle_default_regex_options_element (ParserState *parser_state)
{
xmlNode *elm;
g_return_if_fail (parser_state->error == NULL);
if (parser_state->ctx_data == NULL)
return;
elm = xmlTextReaderCurrentNode (parser_state->reader);
parser_state->regex_compile_flags = get_regex_flags (elm, 0);
}
static void
parse_language_with_id (ParserState *parser_state,
gchar *lang_id)
{
GtkSourceLanguageManager *lm;
GtkSourceLanguage *imported_language;
g_return_if_fail (parser_state->error == NULL);
lm = _gtk_source_language_get_language_manager (parser_state->language);
imported_language = gtk_source_language_manager_get_language (lm, lang_id);
if (imported_language == NULL)
{
g_set_error (&parser_state->error,
PARSER_ERROR,
PARSER_ERROR_WRONG_ID,
"unable to resolve language '%s'",
lang_id);
}
else
{
file_parse (imported_language->priv->lang_file_name,
parser_state->language,
parser_state->ctx_data,
parser_state->defined_regexes,
parser_state->styles_mapping,
parser_state->loaded_lang_ids,
parser_state->replacements,
&parser_state->error);
}
}
static void
parse_style (ParserState *parser_state)
{
gchar *id;
xmlChar *name, *map_to;
xmlChar *tmp;
gchar *lang_id = NULL;
g_return_if_fail (parser_state->error == NULL);
tmp = xmlTextReaderGetAttribute (parser_state->reader,
BAD_CAST "id");
if (id_is_decorated ((gchar*) tmp, NULL))
id = g_strdup ((gchar*) tmp);
else
id = decorate_id (parser_state, (gchar*) tmp);
xmlFree (tmp);
name = xmlTextReaderGetAttribute (parser_state->reader,
BAD_CAST "_name");
if (name != NULL)
{
gchar *tmp2 = _gtk_source_language_translate_string (parser_state->language,
(gchar*) name);
tmp = xmlStrdup (BAD_CAST tmp2);
xmlFree (name);
name = tmp;
g_free (tmp2);
}
else
{
name = xmlTextReaderGetAttribute (parser_state->reader,
BAD_CAST "name");
}
map_to = xmlTextReaderGetAttribute (parser_state->reader,
BAD_CAST "map-to");
if (map_to != NULL && !id_is_decorated ((gchar*) map_to, &lang_id))
{
g_set_error (&parser_state->error,
PARSER_ERROR,
PARSER_ERROR_MALFORMED_MAP_TO,
"the map-to attribute '%s' for the style '%s' lacks the prefix",
map_to, id);
}
if (parser_state->error == NULL && lang_id != NULL && lang_id[0] == 0)
{
g_free (lang_id);
lang_id = NULL;
}
if (parser_state->error == NULL && lang_id != NULL &&
!lang_id_is_already_loaded (parser_state, lang_id))
{
parse_language_with_id (parser_state, lang_id);
}
DEBUG (g_message ("style %s (%s) to be mapped to '%s'",
name, id, map_to ? (char*) map_to : "(null)"));
if (map_to != NULL &&
g_hash_table_lookup (parser_state->styles_mapping, map_to) == NULL)
{
g_warning ("in file %s: style '%s' not defined", parser_state->filename, map_to);
}
if (parser_state->error == NULL)
{
GtkSourceStyleInfo *info;
/* Remember the style name only if the style has been defined in
* the lang file we are parsing */
if (g_str_has_prefix (id, parser_state->language_decoration))
info = _gtk_source_style_info_new ((gchar *) name,
(gchar *) map_to);
else
info = _gtk_source_style_info_new (NULL,
(gchar *) map_to);
g_hash_table_insert (parser_state->styles_mapping, g_strdup (id), info);
}
g_free (lang_id);
g_free (id);
xmlFree (name);
xmlFree (map_to);
}
static void
handle_keyword_char_class_element (ParserState *parser_state)
{
xmlChar *char_class;
int ret, type;
g_return_if_fail (parser_state->error == NULL);
if (parser_state->ctx_data == NULL)
return;
do {
ret = xmlTextReaderRead (parser_state->reader);
g_assert (ret == 1);
type = xmlTextReaderNodeType (parser_state->reader);
}
while (type != XML_READER_TYPE_TEXT && type != XML_READER_TYPE_CDATA);
char_class = xmlTextReaderValue (parser_state->reader);
g_free (parser_state->opening_delimiter);
g_free (parser_state->closing_delimiter);
parser_state->opening_delimiter = g_strdup_printf ("(?!<%s)(?=%s)",
char_class, char_class);
parser_state->closing_delimiter = g_strdup_printf ("(?<=%s)(?!%s)",
char_class, char_class);
xmlFree (char_class);
}
static void
handle_styles_element (ParserState *parser_state)
{
int type;
const xmlChar *tag_name;
g_return_if_fail (parser_state->error == NULL);
while (parser_state->error == NULL)
{
/* FIXME: is xmlTextReaderIsValid call needed here or
* error func will be called? */
xmlTextReaderRead (parser_state->reader);
xmlTextReaderIsValid (parser_state->reader);
if (parser_state->error != NULL)
break;
tag_name = xmlTextReaderConstName (parser_state->reader);
type = xmlTextReaderNodeType (parser_state->reader);
/* End at the closing </styles> tag */
if (tag_name && type == XML_READER_TYPE_END_ELEMENT &&
!xmlStrcmp (BAD_CAST "styles", tag_name))
break;
/* Skip nodes that aren't <style> elements */
if (tag_name == NULL || xmlStrcmp (BAD_CAST "style", tag_name))
continue;
/* Handle <style> elements */
parse_style (parser_state);
}
}
static void
element_start (ParserState *parser_state)
{
const xmlChar *name;
g_return_if_fail (parser_state->error == NULL);
/* TODO: check the namespace and ignore everithing is not in our namespace */
name = xmlTextReaderConstName (parser_state->reader);
if (xmlStrcmp (BAD_CAST "context", name) == 0)
handle_context_element (parser_state);
else if (xmlStrcmp (BAD_CAST "replace", name) == 0)
handle_replace_element (parser_state);
else if (xmlStrcmp (BAD_CAST "define-regex", name) == 0)
handle_define_regex_element (parser_state);
else if (xmlStrcmp (BAD_CAST "language", name) == 0)
handle_language_element (parser_state);
else if (xmlStrcmp (BAD_CAST "styles", name) == 0)
handle_styles_element (parser_state);
else if (xmlStrcmp (BAD_CAST "keyword-char-class", name) == 0)
handle_keyword_char_class_element (parser_state);
else if (xmlStrcmp (BAD_CAST "default-regex-options", name) == 0)
handle_default_regex_options_element (parser_state);
}
static void
element_end (ParserState *parser_state)
{
const xmlChar *name;
gchar *popped_id;
name = xmlTextReaderConstName (parser_state->reader);
if (xmlStrcmp (name, BAD_CAST "context") == 0)
{
/* pop the first element in the curr_parents list */
popped_id = g_queue_pop_head (parser_state->curr_parents);
g_free (popped_id);
}
}
static void
text_reader_structured_error_func (ParserState *parser_state,
xmlErrorPtr error)
{
/* FIXME: does someone know how to use libxml api? */
if (parser_state->error == NULL)
g_set_error (&parser_state->error,
PARSER_ERROR,
PARSER_ERROR_INVALID_DOC,
"in file %s on line %d: %s\n",
error->file,
error->line,
error->message);
else
/* extra warning won't hurt and it could give a clue about what's wrong */
g_warning ("in file %s on line %d: %s\n", error->file, error->line, error->message);
}
static gboolean
file_parse (gchar *filename,
GtkSourceLanguage *language,
GtkSourceContextData *ctx_data,
GHashTable *defined_regexes,
GHashTable *styles,
GHashTable *loaded_lang_ids,
GQueue *replacements,
GError **error)
{
ParserState *parser_state;
xmlTextReader *reader = NULL;
int fd = -1;
GError *tmp_error = NULL;
GtkSourceLanguageManager *lm;
const gchar *rng_lang_schema;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
DEBUG (g_message ("loading file '%s'", filename));
/*
* Use fd instead of filename so that it's utf8 safe on w32.
*/
fd = g_open (filename, O_RDONLY, 0);
if (fd != -1)
reader = xmlReaderForFd (fd, filename, NULL, 0);
if (reader == NULL)
{
g_set_error (&tmp_error,
PARSER_ERROR,
PARSER_ERROR_CANNOT_OPEN,
"unable to open the file");
goto error;
}
lm = _gtk_source_language_get_language_manager (language);
rng_lang_schema = _gtk_source_language_manager_get_rng_file (lm);
if (rng_lang_schema == NULL)
{
g_set_error (&tmp_error,
PARSER_ERROR,
PARSER_ERROR_CANNOT_VALIDATE,
"could not find the RelaxNG schema file");
goto error;
}
if (xmlTextReaderRelaxNGValidate (reader, rng_lang_schema))
{
g_set_error (&tmp_error,
PARSER_ERROR,
PARSER_ERROR_CANNOT_VALIDATE,
"unable to load the RelaxNG schema '%s'",
rng_lang_schema);
goto error;
}
parser_state = parser_state_new (language, ctx_data,
defined_regexes, styles,
replacements, reader,
filename, loaded_lang_ids);
xmlTextReaderSetStructuredErrorHandler (reader,
(xmlStructuredErrorFunc) text_reader_structured_error_func,
parser_state);
while (parser_state->error == NULL &&
xmlTextReaderRead (parser_state->reader) == 1)
{
int type;
/* FIXME: does xmlTextReaderRead already do it? */
xmlTextReaderIsValid (parser_state->reader);
if (parser_state->error != NULL)
break;
type = xmlTextReaderNodeType (parser_state->reader);
switch (type)
{
case XML_READER_TYPE_ELEMENT:
element_start (parser_state);
break;
case XML_READER_TYPE_END_ELEMENT:
element_end (parser_state);
break;
}
}
if (parser_state->error != NULL)
{
g_propagate_error (&tmp_error, parser_state->error);
parser_state->error = NULL;
}
parser_state_destroy (parser_state);
if (tmp_error != NULL)
goto error;
close (fd);
return TRUE;
error:
if (fd != -1)
close (fd);
g_propagate_error (error, tmp_error);
return FALSE;
}
static ParserState *
parser_state_new (GtkSourceLanguage *language,
GtkSourceContextData *ctx_data,
GHashTable *defined_regexes,
GHashTable *styles_mapping,
GQueue *replacements,
xmlTextReader *reader,
const char *filename,
GHashTable *loaded_lang_ids)
{
ParserState *parser_state;
parser_state = g_slice_new0 (ParserState);
parser_state->language = language;
parser_state->ctx_data = ctx_data;
g_return_val_if_fail (language->priv->id != NULL, NULL);
parser_state->language_decoration = g_strdup_printf ("%s:", language->priv->id);
parser_state->current_lang_id = NULL;
parser_state->id_cookie = 0;
parser_state->regex_compile_flags = 0;
parser_state->reader = reader;
parser_state->filename = g_filename_display_name (filename);
parser_state->error = NULL;
parser_state->defined_regexes = defined_regexes;
parser_state->styles_mapping = styles_mapping;
parser_state->replacements = replacements;
parser_state->loaded_lang_ids = loaded_lang_ids;
parser_state->curr_parents = g_queue_new ();
parser_state->opening_delimiter = g_strdup ("\\b");
parser_state->closing_delimiter = g_strdup ("\\b");
return parser_state;
}
static void
parser_state_destroy (ParserState *parser_state)
{
if (parser_state->reader != NULL)
xmlFreeTextReader (parser_state->reader);
if (parser_state->error != NULL)
g_error_free (parser_state->error);
g_queue_free (parser_state->curr_parents);
g_free (parser_state->current_lang_id);
g_free (parser_state->opening_delimiter);
g_free (parser_state->closing_delimiter);
g_free (parser_state->language_decoration);
g_free (parser_state->filename);
g_slice_free (ParserState, parser_state);
}
static gboolean
steal_styles_mapping (gchar *style_id,
GtkSourceStyleInfo *info,
GHashTable *styles)
{
g_hash_table_insert (styles, style_id, info);
return TRUE;
}
gboolean
_gtk_source_language_file_parse_version2 (GtkSourceLanguage *language,
GtkSourceContextData *ctx_data)
{
GHashTable *defined_regexes, *styles;
gboolean success;
GError *error = NULL;
gchar *filename;
GHashTable *loaded_lang_ids;
GQueue *replacements;
g_return_val_if_fail (ctx_data != NULL, FALSE);
filename = language->priv->lang_file_name;
/* TODO: as an optimization tell the parser to merge CDATA
* as text nodes (XML_PARSE_NOCDATA), and to ignore blank
* nodes (XML_PARSE_NOBLANKS), if it is possible with
* xmlTextReader. */
xmlKeepBlanksDefault (0);
xmlLineNumbersDefault (1);
xmlSubstituteEntitiesDefault (1);
DEBUG (xmlPedanticParserDefault (1));
defined_regexes = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_free);
styles = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) _gtk_source_style_info_free);
loaded_lang_ids = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) xmlFree,
NULL);
replacements = g_queue_new ();
success = file_parse (filename, language, ctx_data,
defined_regexes, styles,
loaded_lang_ids, replacements,
&error);
if (success)
success = _gtk_source_context_data_finish_parse (ctx_data, replacements->head, &error);
if (success)
g_hash_table_foreach_steal (styles,
(GHRFunc) steal_styles_mapping,
language->priv->styles);
g_queue_foreach (replacements, (GFunc) _gtk_source_context_replace_free, NULL);
g_queue_free (replacements);
g_hash_table_destroy (loaded_lang_ids);
g_hash_table_destroy (defined_regexes);
g_hash_table_destroy (styles);
if (!success)
{
g_warning ("Failed to load '%s': %s",
filename, error->message);
g_error_free (error);
return FALSE;
}
return TRUE;
}