/* grecs - Gray's Extensible Configuration System
Copyright (C) 2007-2012, 2015 Sergey Poznyakoff
Grecs is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 3 of the License, or (at your
option) any later version.
Grecs 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 General Public License for more details.
You should have received a copy of the GNU General Public License along
with Grecs. If not, see . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include "grecs.h"
#include "wordsplit.h"
#include
static int
_grecs_list_eq(struct grecs_value *a, struct grecs_value *b)
{
struct grecs_list_entry *aent, *bent;
if (grecs_list_size(a->v.list) != grecs_list_size(b->v.list))
return 0;
for (aent = a->v.list->head, bent = b->v.list->head;;
aent = aent->next, bent = bent->next) {
if (!aent)
return bent == NULL;
if (!bent)
return 0;
if (!grecs_value_eq(aent->data, bent->data))
return 0;
}
/*notreached*/
return 1;
}
static int
_grecs_array_eq(struct grecs_value *a, struct grecs_value *b)
{
size_t i;
if (a->v.arg.c != b->v.arg.c)
return 0;
for (i = 0; i < a->v.arg.c; i++)
if (!grecs_value_eq(a->v.arg.v[i], b->v.arg.v[i]))
return 0;
return 1;
}
/* Return 1 if configuration value A equals B */
int
grecs_value_eq(struct grecs_value *a, struct grecs_value *b)
{
if (a == 0 || b == 0)
return a == b;
if (a->type != b->type)
return 0;
switch (a->type) {
case GRECS_TYPE_STRING:
if (a->v.string == NULL)
return b->v.string == NULL;
return strcmp(a->v.string, b->v.string) == 0;
case GRECS_TYPE_LIST:
return _grecs_list_eq(a, b);
case GRECS_TYPE_ARRAY:
return _grecs_array_eq(a, b);
}
return 0;
}
static int
_grecs_list_match(struct grecs_value *pat, struct grecs_value *b, int flags)
{
struct grecs_list_entry *aent, *bent;
if (grecs_list_size(pat->v.list) != grecs_list_size(b->v.list))
return 0;
for (aent = pat->v.list->head, bent = b->v.list->head;;
aent = aent->next, bent = bent->next) {
if (!aent)
return bent == NULL;
if (!bent)
return 0;
if (!grecs_value_match(aent->data, bent->data, flags))
return 0;
}
/*notreached*/
return 1;
}
static int
_grecs_array_match(struct grecs_value *pat, struct grecs_value *b, int flags)
{
size_t i;
if (pat->v.arg.c > b->v.arg.c)
return 0;
for (i = 0; i < pat->v.arg.c; i++)
if (!grecs_value_match(pat->v.arg.v[i], b->v.arg.v[i], flags))
return 0;
return 1;
}
int
grecs_value_match(struct grecs_value *pat, struct grecs_value *b, int flags)
{
if (pat == 0 || b == 0)
return pat == b;
if (pat->type != b->type) {
if (pat->type != GRECS_TYPE_STRING)
return 0;
switch (b->type) {
case GRECS_TYPE_LIST:
b = grecs_list_index(b->v.list, 0);
break;
case GRECS_TYPE_ARRAY:
b = b->v.arg.v[0];
}
}
switch (pat->type) {
case GRECS_TYPE_STRING:
if (pat->v.string == NULL)
return b->v.string == NULL;
return fnmatch(pat->v.string, b->v.string, flags) == 0;
case GRECS_TYPE_LIST:
return _grecs_list_match(pat, b, flags);
case GRECS_TYPE_ARRAY:
return _grecs_array_match(pat, b, flags);
}
return 0;
}
struct grecs_match_buf {
int argc; /* number of path components */
char **argv; /* array of path components */
int argi; /* Index of the current component */
struct grecs_value **labelv; /* Component labels */
struct grecs_node *root; /* Root node */
struct grecs_node *node; /* Last found node */
};
#define ISWC(c,w) ((c)[0] == (w) && (c)[1] == 0)
grecs_match_buf_t
grecs_match_buf_create(int argc, char **argv, struct grecs_value **labelv)
{
int i;
struct grecs_match_buf *buf = grecs_zalloc(sizeof(*buf));
buf->argc = argc;
buf->argv = argv;
buf->labelv = labelv;
/* Compress argv/argc by replacing contiguous sequences of *'s
with a single *. */
for (i = 0; i < buf->argc; i++) {
if (ISWC(buf->argv[i], '*')) {
int j;
for (j = i + 1;
j < buf->argc && ISWC(buf->argv[j], '*'); j++) {
free(buf->argv[j]);
grecs_value_free_content(buf->labelv[i]);
}
j -= i;
if (j > 1) {
memmove(&buf->argv[i+1], &buf->argv[i+j],
(buf->argc - (i + j)) *
sizeof(buf->argv[0]));
memmove(&buf->labelv[i+1], &buf->labelv[i+j],
(buf->argc - (i + j)) *
sizeof(buf->labelv[0]));
buf->argc -= j - 1;
}
}
}
return buf;
}
size_t
grecs_match_buf_get_args(grecs_match_buf_t buf, char ***argv)
{
if (argv)
*argv = buf->argv;
return buf->argc;
}
struct grecs_node *
grecs_match_buf_get_node(grecs_match_buf_t buf)
{
return buf->node;
}
struct grecs_node *
grecs_match_buf_get_root(grecs_match_buf_t buf)
{
return buf->root;
}
void
grecs_match_buf_set_root(grecs_match_buf_t buf, struct grecs_node *root)
{
buf->root = root;
}
static void
grecs_match_buf_free_contents(struct grecs_match_buf *buf)
{
size_t i;
for (i = 0; i < buf->argc; i++) {
free(buf->argv[i]);
grecs_value_free(buf->labelv[i]);
}
free(buf->argv);
free(buf->labelv);
}
void
grecs_match_buf_free(struct grecs_match_buf *buf)
{
if (buf) {
grecs_match_buf_free_contents(buf);
free(buf);
}
}
static struct grecs_value *
parse_label(const char *str)
{
struct grecs_value *val = NULL;
size_t i;
struct wordsplit ws;
size_t len = strlen (str);
if (len > 1 && str[0] == '(' && str[len-1] == ')') {
struct grecs_list *lst;
ws.ws_delim = ",";
if (wordsplit_len (str + 1, len - 2, &ws,
WRDSF_DEFFLAGS|WRDSF_DELIM|
WRDSF_WS)) {
return NULL;
}
lst = grecs_value_list_create();
for (i = 0; i < ws.ws_wordc; i++) {
struct grecs_value *p = grecs_zalloc(sizeof(*p));
p->type = GRECS_TYPE_STRING;
p->v.string = ws.ws_wordv[i];
grecs_list_append(lst, p);
}
val = grecs_malloc(sizeof(*val));
val->type = GRECS_TYPE_LIST;
val->v.list = lst;
} else {
if (wordsplit(str, &ws, WRDSF_DEFFLAGS))
return NULL;
val = grecs_zalloc(sizeof(*val));
if (ws.ws_wordc == 1) {
val->type = GRECS_TYPE_STRING;
val->v.string = ws.ws_wordv[0];
} else {
val->type = GRECS_TYPE_ARRAY;
val->v.arg.c = ws.ws_wordc;
val->v.arg.v = grecs_calloc(ws.ws_wordc,
sizeof(val->v.arg.v[0]));
for (i = 0; i < ws.ws_wordc; i++) {
val->v.arg.v[i] =
grecs_zalloc(sizeof(*val->v.arg.v[0]));
val->v.arg.v[i]->type = GRECS_TYPE_STRING;
val->v.arg.v[i]->v.string = ws.ws_wordv[i];
}
}
}
ws.ws_wordc = 0;
wordsplit_free(&ws);
return val;
}
static int
split_cfg_path(const char *path, int *pargc, char ***pargv,
grecs_value_t ***pvalv)
{
int argc;
char **argv;
char *delim = ".";
char static_delim[2] = { 0, 0 };
if (path[0] == '\\') {
argv = calloc(2, sizeof (*argv));
if (!argv)
return WRDSE_NOSPACE;
argv[0] = strdup(path + 1);
if (!argv[0]) {
free(argv);
return WRDSE_NOSPACE;
}
argv[1] = NULL;
argc = 1;
} else {
int rc;
struct wordsplit ws;
if (strchr("./:;,^~", path[0])) {
delim = static_delim;
delim[0] = path[0];
path++;
}
ws.ws_delim = delim;
rc = wordsplit(path, &ws,
WRDSF_DELIM | WRDSF_DEFFLAGS);
if (rc)
return rc;
argc = ws.ws_wordc;
argv = ws.ws_wordv;
ws.ws_wordc = 0;
ws.ws_wordv = NULL;
wordsplit_free(&ws);
}
*pargv = argv;
*pargc = argc;
if (pvalv) {
int i;
grecs_value_t **valv;
valv = grecs_calloc(argc, sizeof(valv[0]));
for (i = 0; i < argc; i++) {
char *p = strchr(argv[i], '=');
if (p) {
*p++ = 0;
valv[i] = parse_label(p);
}
}
*pvalv = valv;
}
return 0;
}
enum grecs_tree_recurse_res
grecs_node_exact_match(enum grecs_tree_recurse_op op,
struct grecs_node *node, void *data)
{
int match = 0;
struct grecs_match_buf *buf = data;
if (node->type == grecs_node_root)
return grecs_tree_recurse_ok;
if (op == grecs_tree_recurse_post) {
if (buf->argi == 0)
return grecs_tree_recurse_stop;
--buf->argi;
return grecs_tree_recurse_ok;
}
if (strcmp(buf->argv[buf->argi], node->ident) == 0
&& (!buf->labelv[buf->argi] ||
grecs_value_eq(buf->labelv[buf->argi], node->v.value))) {
if (buf->argi + 1 == buf->argc) {
buf->node = node;
return grecs_tree_recurse_stop;
}
match = 1;
}
if (match) {
if (op == grecs_tree_recurse_pre) {
if (buf->argi + 1 == buf->argc)
return grecs_tree_recurse_skip;
buf->argi++;
}
return grecs_tree_recurse_ok;
}
return node->type == grecs_node_block ?
grecs_tree_recurse_skip : grecs_tree_recurse_ok;
}
struct grecs_node *
grecs_find_node(struct grecs_node *node, const char *path)
{
int rc;
struct grecs_match_buf buf;
if (strcmp(path, ".") == 0)
return node;
rc = split_cfg_path(path, &buf.argc, &buf.argv, &buf.labelv);
if (rc || !buf.argc)
return NULL;
buf.argi = 0;
buf.node = NULL;
grecs_tree_recurse(node, grecs_node_exact_match, &buf);
grecs_match_buf_free_contents(&buf);
return buf.node;
}
static void
fixup_loci(struct grecs_node *node,
grecs_locus_t const *plocus,
struct grecs_locus_point const *endp)
{
grecs_locus_t loc = *plocus;
for (; node; node = node->down) {
node->idloc = loc;
node->locus = loc;
if (endp)
node->locus.end = *endp;
}
}
struct grecs_node *
grecs_node_from_path_locus(const char *path, const char *value,
grecs_locus_t *plocus, grecs_locus_t *vallocus)
{
int rc;
int i;
int argc;
char **argv;
struct grecs_node *dn = NULL;
rc = split_cfg_path(path, &argc, &argv, NULL);
if (rc)
return NULL;
dn = grecs_node_create(grecs_node_stmt, NULL);
dn->ident = argv[argc - 1];
if (value) {
struct grecs_value *gval = parse_label(value);
if (vallocus)
gval->locus = *vallocus;
dn->v.value = gval;
} else
dn->v.value = NULL;
for (i = argc - 2; i >= 0; i--) {
struct grecs_value *label = NULL;
struct grecs_node *node;
char *p, *q = argv[i];
do {
p = strchr(q, '=');
if (p && p > argv[i] && p[-1] != '\\') {
*p++ = 0;
label = parse_label(p);
break;
} else if (p)
q = p + 1;
else
break;
} while (*q);
node = grecs_node_create(grecs_node_block, plocus);
node->ident = argv[i];
if (label)
node->v.value = label;
node->down = dn;
if (dn)
dn->up = node;
dn = node;
}
if (plocus)
fixup_loci(dn,
plocus, vallocus ? &vallocus->end : NULL);
free(argv);
return dn;
}
struct grecs_node *
grecs_node_from_path(const char *path, const char *value)
{
return grecs_node_from_path_locus(path, value, NULL, NULL);
}
static int
is_root(struct grecs_match_buf *buf, struct grecs_node *node)
{
return (buf->root == node || node->type == grecs_node_root);
}
static int
grecs_match(struct grecs_match_buf *buf)
{
struct grecs_node *node;
int wcard = 0;
buf->argi = buf->argc - 1;
node = buf->node;
while (buf->argi >= 0) {
if (ISWC(buf->argv[buf->argi], '*')) {
wcard = 1;
if (buf->argi-- == 0)
return 1;
continue;
}
if ((ISWC(buf->argv[buf->argi], '%') ||
strcmp(buf->argv[buf->argi], node->ident) == 0)
/* FIXME: */
&& (!buf->labelv[buf->argi] ||
grecs_value_match(buf->labelv[buf->argi],
node->v.value, 0))) {
wcard = 0;
node = node->up;
if (buf->argi-- == 0)
return is_root(buf, node);
} else if (!wcard)
return 0;
else
node = node->up;
if (is_root(buf, node))
return ISWC(buf->argv[buf->argi], '*');
}
return 0;
}
struct grecs_node *
grecs_match_next(struct grecs_match_buf *buf)
{
if (!buf)
return NULL;
while ((buf->node = grecs_next_node(buf->node)))
if (grecs_match(buf))
break;
return buf->node;
}
struct grecs_node *
grecs_match_buf_first(struct grecs_match_buf *buf, struct grecs_node *tree)
{
struct grecs_node *node;
buf->argi = 0;
buf->root = tree;
buf->node = grecs_tree_first_node(tree);
if (!buf->node)
return NULL;
if (grecs_match(buf))
node = buf->node;
else
node = grecs_match_next(buf);
return node;
}
struct grecs_node *
grecs_match_first(struct grecs_node *tree, const char *pattern,
struct grecs_match_buf **pbuf)
{
struct grecs_node *node;
struct grecs_match_buf *buf;
if (strcmp(pattern, ".") == 0) {
*pbuf = NULL;
return tree;
}
buf = grecs_zalloc(sizeof(*buf));
if (split_cfg_path(pattern, &buf->argc, &buf->argv, &buf->labelv)) {
free(buf);
return NULL;
}
node = grecs_match_buf_first(buf, tree);
if (node)
*pbuf = buf;
else {
grecs_match_buf_free(buf);
*pbuf = NULL;
}
return node;
}