summaryrefslogtreecommitdiffabout
authorSergey Poznyakoff <gray@gnu.org.ua>2011-05-03 14:49:29 (GMT)
committer Sergey Poznyakoff <gray@gnu.org.ua>2011-05-03 18:23:21 (GMT)
commit3d679b3df641f59fb81ca1651799f4e2965ed67e (patch) (side-by-side diff)
tree5a614ee25cff44d015ee9e6f6920e2ba19379bba
parent24ec67c9f6375d34d88e79981ed8abbe15a78169 (diff)
downloadgrecs-3d679b3df641f59fb81ca1651799f4e2965ed67e.tar.gz
grecs-3d679b3df641f59fb81ca1651799f4e2965ed67e.tar.bz2
Switch to the two-layer model. Add testsuite.
The configuration file parser creates a syntax tree. This step does not require any knowledge about which keywords are allowed. The user can then either use that tree directly, or post-process it using parser tables. The latter approach is equivalent to previous versions of grecs.
Diffstat (more/less context) (ignore whitespace changes)
-rw-r--r--.gitignore29
-rw-r--r--Makefile.am2
-rw-r--r--am/grecs.m421
-rw-r--r--src/.gitignore4
-rw-r--r--src/Makefile.am3
-rw-r--r--src/diag.c73
-rw-r--r--src/format.c121
-rw-r--r--src/grecs-gram.y715
-rw-r--r--src/grecs.h86
-rw-r--r--src/lookup.c272
-rw-r--r--src/tree.c773
-rw-r--r--tests/.gitignore8
-rw-r--r--tests/Makefile.am84
-rw-r--r--tests/atlocal.in10
-rw-r--r--tests/cfhelp.at76
-rw-r--r--tests/format00.at38
-rw-r--r--tests/format01.at38
-rw-r--r--tests/format02.at38
-rw-r--r--tests/gcf1.conf54
-rw-r--r--tests/gcffmt.c67
-rw-r--r--tests/gcfpeek.c76
-rw-r--r--tests/gcfset.c240
-rw-r--r--tests/peek00.at25
-rw-r--r--tests/peek01.at30
-rw-r--r--tests/peek02.at26
-rw-r--r--tests/peek03.at33
-rw-r--r--tests/set.at38
-rw-r--r--tests/testsuite.at54
28 files changed, 2318 insertions, 716 deletions
diff --git a/.gitignore b/.gitignore
index 42c88b2..eec45d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,25 @@
-gconf-gram.c
-gconf-gram.h
-gconf-gram.output
-gconf-lex.c
+*.a
+*.o
+*.tar.*
+*~
+.deps
+.emacs.desktop
+.emacs.desktop.lock
+.emacsrc
+ABOUT-NLS
+ChangeLog
+INSTALL
+Makefile
+Makefile.in
+TAGS
+aclocal.m4
+autom4te.cache
+build-aux
+config.h
+config.h.in
+config.log
+config.status
+configure
+core
+m4
+stamp-h1
diff --git a/Makefile.am b/Makefile.am
index 1bfdcf4..17f1ff6 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1 +1 @@
-SUBDIRS=src
+SUBDIRS=src @GRECS_TESTDIR@
diff --git a/am/grecs.m4 b/am/grecs.m4
index 47c9867..5fc1819 100644
--- a/am/grecs.m4
+++ b/am/grecs.m4
@@ -49,14 +49,15 @@ AC_DEFUN([_GRECS_OPTION_SWITCH],
AC_DEFUN([_GRECS_SET_OPTIONS],
[m4_foreach_w([_GRECS_Option], [$1], [_GRECS_SET_OPTION(_GRECS_Option)])])
-# GRECS_SETUP([OPTIONS],[pp-setup-file])
-#
-# Options are:
+# GRECS_SETUP([dir],[OPTIONS],[pp-setup-file])
+# dir - Directory in the source tree where grecs has been cloned.
+# OPTIONS are:
# no-preproc Disable the use of preprocessor.
# std-pp-setup Install standard pp-setup file.
# pp-setup-option Add the --with-pp-setup-file option to the
# configuration file. The option allows user to
# control whether the pp-setup file is installed.
+# tests Build tests.
#
# The pp-setup-file argument supplies the pathname of the preprocessor
# setup file in the source tree. It is ignored if std-pp-setup option is
@@ -69,7 +70,8 @@ AC_DEFUN([GRECS_SETUP],[
AC_PROG_YACC
AM_PROG_LEX
- _GRECS_SET_OPTIONS([$1])
+ AC_SUBST([GRECS_SUBDIR],m4_if([$1],,grecs,$1))
+ _GRECS_SET_OPTIONS([$2])
# **********************
# Preprocessor
# **********************
@@ -104,7 +106,7 @@ AC_DEFUN([GRECS_SETUP],[
DEFAULT_PREPROCESSOR="$DEFAULT_PREPROCESSOR $PREPROC_OPTIONS"
_GRECS_IF_OPTION_SET([std-pp-setup],
[PP_SETUP_FILE='pp-setup'],
- [m4_if([$2],,[PP_SETUP_FILE=],[PP_SETUP_FILE='$2'])])
+ [m4_if([$3],,[PP_SETUP_FILE=],[PP_SETUP_FILE='$3'])])
AC_SUBST(PP_SETUP_FILE)
if test -n "$PP_SETUP_FILE"; then
_GRECS_IF_OPTION_SET([pp-setup-option],
@@ -127,5 +129,14 @@ AC_DEFUN([GRECS_SETUP],[
else
DEFAULT_PREPROCESSOR=NULL
fi
+ _GRECS_IF_OPTION_SET([tests],
+ [m4_pushdef([TESTDIR],m4_if([$1],,grecs,$1)/tests)
+ AC_CONFIG_TESTDIR(TESTDIR)
+ AC_CONFIG_FILES(TESTDIR/Makefile TESTDIR/atlocal)
+ m4_popdef([TESTDIR])
+ AM_MISSING_PROG([AUTOM4TE], [autom4te])
+ GRECS_TESTDIR=tests
+ ])
AC_SUBST([GRECS_INCLUDES])
+ AC_SUBST([GRECS_TESTDIR])
])
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..7c38320
--- a/dev/null
+++ b/src/.gitignore
@@ -0,0 +1,4 @@
+grecs-gram.c
+grecs-gram.h
+grecs-gram.output
+grecs-lex.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 759716b..0c2c444 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -16,14 +16,17 @@
noinst_LIBRARIES=libgrecs.a
libgrecs_a_SOURCES = \
+ diag.c\
format.c\
grecs-gram.y\
grecs-lex.l\
list.c\
+ lookup.c\
mem.c\
preproc.c\
symtab.c\
text.c\
+ tree.c\
grecs.h\
wordsplit.c\
wordsplit.h
diff --git a/src/diag.c b/src/diag.c
new file mode 100644
index 0000000..fcb8fd2
--- a/dev/null
+++ b/src/diag.c
@@ -0,0 +1,73 @@
+/* grecs - Gray's Extensible Configuration System
+ Copyright (C) 2007-2011 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <grecs.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+static void
+default_print_diag(grecs_locus_t *locus, int err, int errcode, const char *msg)
+{
+ if (locus)
+ fprintf(stderr, "%s:%d: ", locus->file, locus->line);
+ if (!err)
+ fprintf(stderr, "warning: ");
+ fprintf(stderr, "%s", msg);
+ if (errcode)
+ fprintf(stderr, ": %s", strerror(errno));
+ fputc('\n', stderr);
+}
+
+void (*grecs_print_diag_fun)(grecs_locus_t *, int, int, const char *msg) =
+ default_print_diag;
+
+void
+grecs_warning(grecs_locus_t *locus, int errcode, const char *fmt, ...)
+{
+ va_list ap;
+ char *buf = NULL;
+ size_t size = 0;
+
+ va_start(ap, fmt);
+ if (grecs_vasprintf(&buf, &size, fmt, ap))
+ grecs_alloc_die();
+ va_end(ap);
+ grecs_print_diag_fun(locus, 0, errcode, buf);
+ free(buf);
+}
+
+void
+grecs_error(grecs_locus_t *locus, int errcode, const char *fmt, ...)
+{
+ va_list ap;
+ char *buf = NULL;
+ size_t size = 0;
+
+ va_start(ap, fmt);
+ if (grecs_vasprintf(&buf, &size, fmt, ap))
+ grecs_alloc_die();
+ va_end(ap);
+ grecs_print_diag_fun(locus, 1, errcode, buf);
+ free(buf);
+ grecs_error_count++;
+}
+
+
diff --git a/src/format.c b/src/format.c
index fc6c8d6..11b405a 100644
--- a/src/format.c
+++ b/src/format.c
@@ -24,7 +24,7 @@
#include <string.h>
const char *
-grecs_data_type_string (enum grecs_data_type type)
+grecs_data_type_string(enum grecs_data_type type)
{
switch (type) {
case grecs_type_void:
@@ -68,14 +68,14 @@ grecs_data_type_string (enum grecs_data_type type)
}
static void
-format_level(FILE *stream, unsigned level)
+format_level(unsigned level, FILE *stream)
{
while (level--)
fprintf(stream, " ");
}
void
-grecs_format_docstring(FILE *stream, const char *docstring, unsigned level)
+grecs_format_docstring(const char *docstring, unsigned level, FILE *stream)
{
size_t len = strlen(docstring);
int width = 78 - level * 2;
@@ -101,7 +101,7 @@ grecs_format_docstring(FILE *stream, const char *docstring, unsigned level)
if (seglen == 0 || *p == 0)
seglen = p - docstring;
- format_level(stream, level);
+ format_level(level, stream);
fprintf(stream, "# ");
fwrite(docstring, seglen, 1, stream);
fputc('\n', stream);
@@ -119,14 +119,14 @@ grecs_format_docstring(FILE *stream, const char *docstring, unsigned level)
}
void
-grecs_format_simple_statement(FILE *stream, struct grecs_keyword *kwp,
- unsigned level)
+grecs_format_simple_statement(struct grecs_keyword *kwp, unsigned level,
+ FILE *stream)
{
const char *argstr;
if (kwp->docstring)
- grecs_format_docstring(stream, kwp->docstring, level);
- format_level(stream, level);
+ grecs_format_docstring(kwp->docstring, level, stream);
+ format_level(level, stream);
if (kwp->argname)
argstr = kwp->argname;
@@ -136,7 +136,7 @@ grecs_format_simple_statement(FILE *stream, struct grecs_keyword *kwp,
if (strchr("<[", argstr[0]))
fprintf(stream, "%s %s;\n", kwp->ident, gettext(argstr));
else if (strchr (argstr, ':'))
- fprintf (stream, "%s <%s>;\n", kwp->ident, gettext(argstr));
+ fprintf(stream, "%s <%s>;\n", kwp->ident, gettext(argstr));
else {
fprintf(stream, "%s <%s: ", kwp->ident, gettext(argstr));
if (GRECS_IS_LIST(kwp->type))
@@ -151,34 +151,117 @@ grecs_format_simple_statement(FILE *stream, struct grecs_keyword *kwp,
}
void
-grecs_format_block_statement(FILE *stream, struct grecs_keyword *kwp,
- unsigned level)
+grecs_format_block_statement(struct grecs_keyword *kwp, unsigned level,
+ FILE *stream)
{
if (kwp->docstring)
- grecs_format_docstring(stream, kwp->docstring, level);
- format_level(stream, level);
+ grecs_format_docstring(kwp->docstring, level, stream);
+ format_level(level, stream);
fprintf(stream, "%s", kwp->ident);
if (kwp->argname)
fprintf(stream, " <%s>", gettext(kwp->argname));
fprintf(stream, " {\n");
- grecs_format_statement_array(stream, kwp->kwd, 0, level + 1);
- format_level(stream, level);
+ grecs_format_statement_array(kwp->kwd, 0, level + 1, stream);
+ format_level(level, stream);
fprintf(stream, "}\n");
}
void
-grecs_format_statement_array(FILE *stream, struct grecs_keyword *kwp,
+grecs_format_statement_array(struct grecs_keyword *kwp,
unsigned n,
- unsigned level)
+ unsigned level,
+ FILE *stream)
{
for (; kwp->ident; kwp++, n++) {
if (n)
fputc('\n', stream);
if (kwp->type == grecs_type_section)
- grecs_format_block_statement(stream, kwp, level);
+ grecs_format_block_statement(kwp, level, stream);
else
- grecs_format_simple_statement(stream, kwp, level);
+ grecs_format_simple_statement(kwp, level, stream);
}
}
+
+void
+grecs_format_locus(grecs_locus_t *locus, FILE *fp)
+{
+ fprintf(fp, "%s:%d:", locus->file, locus->line);
+}
+
+void
+grecs_format_node_ident(struct grecs_node *node, int delim, FILE *fp)
+{
+ if (node->up)
+ grecs_format_node_ident(node->up, delim, fp);
+ fputc(delim, fp);
+ fprintf(fp, "%s", node->ident);
+ if (node->type == grecs_node_block &&
+ !GRECS_VALUE_EMPTY_P(&node->value)) {
+ fputc('=', fp);
+ grecs_format_value(&node->value, fp);
+ }
+}
+
+void
+grecs_format_value(struct grecs_value *val, FILE *fp)
+{
+ int i;
+ struct grecs_list_entry *ep;
+
+ switch (val->type) {
+ case GRECS_TYPE_STRING:
+ fprintf(fp, "\"%s\"", val->v.string); /*FIXME: Quoting*/
+ break;
+
+ case GRECS_TYPE_LIST:
+ fputc('(', fp);
+ for (ep = val->v.list->head; ep; ep = ep->next) {
+ grecs_format_value(ep->data, fp);
+ if (ep->next) {
+ fputc(',', fp);
+ fputc(' ', fp);
+ }
+ }
+ fputc(')', fp);
+ break;
+
+ case GRECS_TYPE_ARRAY:
+ for (i = 0; i < val->v.arg.c; i++) {
+ if (i)
+ fputc(' ', fp);
+ grecs_format_value(&val->v.arg.v[i], fp);
+ }
+ }
+}
+
+void
+grecs_format_node(struct grecs_node *node, int flags, FILE *fp)
+{
+ int delim = flags & 0xff;
+
+ if (!delim)
+ delim = '.';
+ switch (node->type) {
+ case grecs_node_block:
+ for (node = node->down; node; node = node->next) {
+ grecs_format_node(node, flags, fp);
+ if (node->next)
+ fputc('\n', fp);
+ }
+ break;
+
+ case grecs_node_stmt:
+ if (flags & GRECS_NODE_FLAG_LOCUS) {
+ grecs_format_locus(&node->locus, fp);
+ fputc(' ', fp);
+ }
+ grecs_format_node_ident(node, delim, fp);
+ fputc(':', fp);
+ fputc(' ', fp);
+ grecs_format_value(&node->value, fp);
+ }
+}
+
+
diff --git a/src/grecs-gram.y b/src/grecs-gram.y
index f65e2dd..c2429a4 100644
--- a/src/grecs-gram.y
+++ b/src/grecs-gram.y
@@ -22,45 +22,22 @@
#include <grecs-gram.h>
#include <stdlib.h>
#include <stdarg.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/time.h>
-#include <sys/un.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#include <netdb.h>
#include <string.h>
#include <errno.h>
-typedef union
-{
- struct sockaddr s;
- struct sockaddr_in s_in;
- struct sockaddr_un s_un;
-} sockaddr_union_t;
-
-static struct grecs_keyword config_keywords;
-static struct grecs_keyword *cursect;
-#define CURRENT_BASE ((char*)(cursect ? cursect->callback_data : NULL))
-static struct grecs_list *sections;
+static struct grecs_node *parse_tree;
int grecs_error_count;
int grecs_default_port = 0;
-static void *target_ptr(struct grecs_keyword *kwp, char *base);
-static void stmt_begin(struct grecs_keyword *kwp, grecs_value_t tag);
-static void stmt_end(struct grecs_keyword *kwp);
-static struct grecs_keyword *find_keyword(const char *ident);
-
-static void process_ident(struct grecs_keyword *kwp, grecs_value_t *value);
-static struct grecs_list *simple_list_create(int dispose);
%}
%union {
- char *string;
- grecs_value_t value;
- struct grecs_list *list;
- struct grecs_keyword *kw;
+ char *string;
+ grecs_value_t value;
+ struct grecs_list *list;
+ struct grecs_node *node;
+ struct { struct grecs_node *head, *tail; } node_list;
}
%token <string> IDENT STRING QSTRING MSTRING
@@ -68,39 +45,47 @@ static struct grecs_list *simple_list_create(int dispose);
%type <list> slist0
%type <value> value tag vallist
%type <list> values list vlist
-%type <kw> ident
+%type <node> stmt simple block
+%type <node_list> stmtlist
%%
input : stmtlist
+ {
+ parse_tree = $1.head;
+ }
;
stmtlist: stmt
+ {
+ $$.head = $$.tail = $1;
+ }
| stmtlist stmt
+ {
+ grecs_node_bind($1.tail, $2, 0);
+ }
;
stmt : simple
| block
;
-simple : ident vallist ';'
+simple : IDENT vallist ';'
{
- process_ident($1, &$2);
+ $$ = grecs_node_create(grecs_node_stmt,
+ &grecs_current_locus);
+ $$->ident = $1;
+ $$->value = $2;
}
;
-block : ident tag { stmt_begin($<kw>1, $<value>2); } '{' stmtlist '}' opt_sc
+block : IDENT tag '{' stmtlist '}' opt_sc
{
- stmt_end($1);
- }
- ;
-
-ident : IDENT
- {
- $$ = find_keyword($1);
- if (!$$)
- grecs_error(&grecs_current_locus, 0,
- _("Unknown keyword"));
+ $$ = grecs_node_create(grecs_node_block,
+ &grecs_current_locus);
+ $$->ident = $1;
+ $$->value = $2;
+ grecs_node_bind($$, $4.head, 1);
}
;
@@ -135,7 +120,7 @@ vallist : vlist
vlist : value
{
- $$ = simple_list_create(0);
+ $$ = _grecs_simple_list_create(0);
grecs_list_append($$, grecs_value_dup(&$1));
}
| vlist value
@@ -169,7 +154,6 @@ string : STRING
slist : slist0
{
struct grecs_list_entry *ep;
- const void *p;
grecs_line_begin();
for (ep = $1->head; ep; ep = ep->next)
@@ -181,7 +165,7 @@ slist : slist0
slist0 : QSTRING
{
- $$ = simple_list_create(0);
+ $$ = _grecs_simple_list_create(0);
grecs_list_append($$, $1);
}
| slist0 QSTRING
@@ -207,7 +191,7 @@ list : '(' ')'
values : value
{
- $$ = simple_list_create(0);
+ $$ = _grecs_simple_list_create(0);
grecs_list_append($$, grecs_value_dup(&$1));
}
| values ',' value
@@ -236,8 +220,8 @@ listel_dispose(void *el)
free(el);
}
-static struct grecs_list *
-simple_list_create(int dispose)
+struct grecs_list *
+_grecs_simple_list_create(int dispose)
{
struct grecs_list *lp = grecs_list_create();
if (dispose)
@@ -307,59 +291,20 @@ grecs_asprintf(char **pbuf, size_t *psize, const char *fmt, ...)
return rc;
}
-void
-grecs_warning(grecs_locus_t *locus, int errcode, const char *fmt, ...)
-{
- va_list ap;
- char *buf = NULL;
- size_t size = 0;
-
- va_start(ap, fmt);
- if (grecs_vasprintf(&buf, &size, fmt, ap))
- grecs_alloc_die();
- va_end(ap);
- grecs_print_diag(locus, 0, errcode, buf);
- free(buf);
-}
-
-void
-grecs_error(grecs_locus_t *locus, int errcode, const char *fmt, ...)
-{
- va_list ap;
- char *buf = NULL;
- size_t size = 0;
-
- va_start(ap, fmt);
- if (grecs_vasprintf(&buf, &size, fmt, ap))
- grecs_alloc_die();
- va_end(ap);
- grecs_print_diag(locus, 1, errcode, buf);
- free(buf);
- grecs_error_count++;
-}
-
-void
-grecs_set_keywords(struct grecs_keyword *kwd)
-{
- config_keywords.kwd = kwd;
-}
-
-int
+struct grecs_node *
grecs_parse(const char *name)
{
int rc;
if (grecs_lex_begin(name))
- return 1;
- cursect = &config_keywords;
- if (sections) {
- grecs_list_free(sections);
- sections = NULL;
- }
+ return NULL;
+ parse_tree = NULL;
rc = yyparse();
grecs_lex_end();
- if (grecs_error_count)
- rc = 1;
- return rc;
+ if (grecs_error_count) {
+ grecs_tree_free(parse_tree);
+ parse_tree = NULL;
+ }
+ return parse_tree;
}
void
@@ -370,583 +315,5 @@ grecs_gram_trace(int n)
-static void *
-target_ptr(struct grecs_keyword *kwp, char *base)
-{
- if (kwp->varptr)
- base = (char*) kwp->varptr + kwp->offset;
- else if (base)
- base += kwp->offset;
-
- return base;
-}
-
-static int
-fake_callback(enum grecs_callback_command cmd,
- grecs_locus_t *locus,
- void *varptr,
- grecs_value_t *value,
- void *cb_data)
-{
- return 0;
-}
-
-static struct grecs_keyword fake = {
- "*",
- NULL,
- NULL,
- grecs_type_void,
- NULL,
- 0,
- fake_callback,
- NULL,
- &fake
-};
-
-static void
-stmt_begin(struct grecs_keyword *kwp, grecs_value_t tag)
-{
- void *target;
-
- if (!sections)
- sections = grecs_list_create();
- grecs_list_push(sections, cursect);
- if (kwp) {
- target = target_ptr(kwp, CURRENT_BASE);
- cursect = kwp;
- if (kwp->callback &&
- kwp->callback(grecs_callback_section_begin,
- &grecs_current_locus, /* FIXME */
- target,
- &tag,
- &kwp->callback_data))
- cursect = &fake;
- } else
- /* install "ignore-all" section */
- cursect = kwp;
-}
-
-static void
-stmt_end(struct grecs_keyword *kwp)
-{
- grecs_callback_fn callback = NULL;
- void *dataptr = NULL;
-
- if (cursect && cursect->callback) {
- callback = cursect->callback;
- dataptr = &cursect->callback_data;
- }
-
- cursect = (struct grecs_keyword *) grecs_list_pop(sections);
- if (!cursect)
- abort();
- if (callback)
- callback(grecs_callback_section_end,
- &grecs_current_locus, /* FIXME */
- kwp ? target_ptr(kwp, CURRENT_BASE) : NULL,
- NULL,
- dataptr);
-}
-
-static struct grecs_keyword *
-find_keyword(const char *ident)
-{
- struct grecs_keyword *kwp;
-
- if (cursect && cursect != &fake) {
- for (kwp = cursect->kwd; kwp->ident; kwp++)
- if (strcmp(kwp->ident, ident) == 0)
- return kwp;
- } else {
- return &fake;
- }
- return NULL;
-}
-
-static int
-string_to_bool(const char *string, int *pval, grecs_locus_t *locus)
-{
- if (strcmp(string, "yes") == 0
- || strcmp(string, "true") == 0
- || strcmp(string, "t") == 0
- || strcmp(string, "1") == 0)
- *pval = 1;
- else if (strcmp(string, "no") == 0
- || strcmp(string, "false") == 0
- || strcmp(string, "nil") == 0
- || strcmp(string, "0") == 0)
- *pval = 0;
- else {
- grecs_error(locus, 0,
- _("%s: not a valid boolean value"),
- string);
- return 1;
- }
- return 0;
-}
-
-static int
-string_to_host(struct in_addr *in, const char *string, grecs_locus_t *locus)
-{
- if (inet_aton(string, in) == 0) {
- struct hostent *hp;
-
- hp = gethostbyname(string);
- if (hp == NULL)
- return 1;
- memcpy(in, hp->h_addr, sizeof(struct in_addr));
- }
- return 0;
-}
-
-static int
-string_to_sockaddr(struct grecs_sockaddr *sp, const char *string,
- grecs_locus_t *locus)
-{
- if (string[0] == '/') {
- struct sockaddr_un s_un;
- if (strlen(string) >= sizeof(s_un.sun_path)) {
- grecs_error(locus, 0,
- _("%s: UNIX socket name too long"),
- string);
- return 1;
- }
- s_un.sun_family = AF_UNIX;
- strcpy(s_un.sun_path, string);
- sp->len = sizeof(s_un);
- sp->sa = grecs_malloc(sp->len);
- memcpy(sp->sa, &s_un, sp->len);
- } else {
- char *p = strchr(string, ':');
- size_t len;
- struct sockaddr_in sa;
-
- sa.sin_family = AF_INET;
- if (p)
- len = p - string;
- else
- len = strlen(string);
-
- if (len == 0)
- sa.sin_addr.s_addr = INADDR_ANY;
- else {
- char *host = grecs_malloc(len + 1);
- memcpy(host, string, len);
- host[len] = 0;
-
- if (string_to_host(&sa.sin_addr, host, locus)) {
- grecs_error(locus, 0,
- _("%s: not a valid IP address or hostname"),
- host);
- free(host);
- return 1;
- }
- free(host);
- }
-
- if (p) {
- struct servent *serv;
-
- p++;
- serv = getservbyname(p, "tcp");
- if (serv != NULL)
- sa.sin_port = serv->s_port;
- else {
- unsigned long l;
- char *q;
-
- /* Not in services, maybe a number? */
- l = strtoul(p, &q, 0);
-
- if (*q || l > USHRT_MAX) {
- grecs_error(locus, 0,
- _("%s: not a valid port number"), p);
- return 1;
- }
- sa.sin_port = htons(l);
- }
- } else if (grecs_default_port)
- sa.sin_port = grecs_default_port;
- else {
- grecs_error(locus, 0, _("missing port number"));
- return 1;
- }
- sp->len = sizeof(sa);
- sp->sa = grecs_malloc(sp->len);
- memcpy(sp->sa, &sa, sp->len);
- }
- return 0;
-}
-
-
-/* The TYPE_* defines come from gnulib's intprops.h */
-
-/* True if the arithmetic type T is signed. */
-# define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
-
-/* The maximum and minimum values for the integer type T. These
- macros have undefined behavior if T is signed and has padding bits. */
-# define TYPE_MINIMUM(t) \
- ((t) (! TYPE_SIGNED(t) \
- ? (t) 0 \
- : TYPE_SIGNED_MAGNITUDE(t) \
- ? ~ (t) 0 \
- : ~ TYPE_MAXIMUM(t)))
-# define TYPE_MAXIMUM(t) \
- ((t) (! TYPE_SIGNED(t) \
- ? (t) -1 \
- : ((((t) 1 << (sizeof(t) * CHAR_BIT - 2)) - 1) * 2 + 1)))
-# define TYPE_SIGNED_MAGNITUDE(t) ((t) ~ (t) 0 < (t) -1)
-
-
-#define STRTONUM(s, type, base, res, limit, loc) \
- { \
- type sum = 0; \
- \
- for (; *s; s++) \
- { \
- type x; \
- \
- if ('0' <= *s && *s <= '9') \
- x = sum * base + *s - '0'; \
- else if (base == 16 && 'a' <= *s && *s <= 'f') \
- x = sum * base + *s - 'a'; \
- else if (base == 16 && 'A' <= *s && *s <= 'F') \
- x = sum * base + *s - 'A'; \
- else \
- break; \
- if (x <= sum) \
- { \
- grecs_error(loc, 0, _("numeric overflow")); \
- return 1; \
- } \
- else if (limit && x > limit) \
- { \
- grecs_error(loc, 0, _("value out of allowed range")); \
- return 1; \
- } \
- sum = x; \
- } \
- res = sum; \
- }
-
-#define STRxTONUM(s, type, res, limit, loc) \
- { \
- int base; \
- if (*s == '0') \
- { \
- s++; \
- if (*s == 0) \
- base = 10; \
- else if (*s == 'x' || *s == 'X') \
- { \
- s++; \
- base = 16; \
- } \
- else \
- base = 8; \
- } else \
- base = 10; \
- STRTONUM(s, type, base, res, limit, loc); \
- }
-
-#define GETUNUM(str, type, res, loc) \
- { \
- type tmpres; \
- const char *s = str; \
- STRxTONUM(s, type, tmpres, 0, loc); \
- if (*s) \
- { \
- grecs_error(loc, 0, _("not a number (stopped near `%s')"), \
- s); \
- return 1; \
- } \
- res = tmpres; \
- }
-
-#define GETSNUM(str, type, res, loc) \
- { \
- unsigned type tmpres; \
- const char *s = str; \
- int sign; \
- unsigned type limit; \
- \
- if (*s == '-') \
- { \
- sign = 1; \
- s++; \
- limit = TYPE_MINIMUM(type); \
- limit = - limit; \
- } \
- else \
- { \
- sign = 0; \
- limit = TYPE_MAXIMUM(type); \
- } \
- \
- STRxTONUM(s, unsigned type, tmpres, limit, loc); \
- if (*s) \
- { \
- grecs_error(loc, 0, _("not a number (stopped near `%s')"), s); \
- return 1; \
- } \
- res = sign ? - tmpres : tmpres; \
- }
-
-
-int
-grecs_string_convert(void *target, enum grecs_data_type type,
- const char *string, grecs_locus_t *locus)
-{
- switch (type) {
- case grecs_type_void:
- abort();
-
- case grecs_type_string:
- *(const char**)target = string;
- break;
-
- case grecs_type_short:
- GETUNUM(string, short, *(short*)target, locus);
- break;
-
- case grecs_type_ushort:
- GETUNUM(string, unsigned short, *(unsigned short*)target, locus);
- break;
-
- case grecs_type_bool:
- return string_to_bool(string, (int*)target, locus);
-
- case grecs_type_int:
- GETSNUM(string, int, *(int*)target, locus);
- break;
-
- case grecs_type_uint:
- GETUNUM(string, unsigned int, *(unsigned int*)target, locus);
- break;
-
- case grecs_type_long:
- GETSNUM(string, long, *(long*)target, locus);
- break;
-
- case grecs_type_ulong:
- GETUNUM(string, unsigned long, *(unsigned long*)target, locus);
- break;
-
- case grecs_type_size:
- GETUNUM(string, size_t, *(size_t*)target, locus);
- break;
- /*FIXME
- case grecs_type_off:
- GETSNUM(string, off_t, *(off_t*)target, locus);
- break;
- */
- case grecs_type_time:
- /*FIXME: Use getdate */
- GETUNUM(string, time_t, *(time_t*)target, locus);
- break;
-
- case grecs_type_ipv4:
- if (inet_aton(string, (struct in_addr *)target)) {
- grecs_error(locus, 0, _("%s: not a valid IP address"),
- string);
- return 1;
- }
- break;
-
- case grecs_type_host:
- if (string_to_host((struct in_addr *)target, string, locus)) {
- grecs_error(locus, 0,
- _("%s: not a valid IP address or hostname"),
- string);
- return 1;
- }
- break;
-
- case grecs_type_sockaddr:
- return string_to_sockaddr((struct grecs_sockaddr *)target, string,
- locus);
-
- /* FIXME: */
- case grecs_type_cidr:
- grecs_error(locus, 0,
- _("INTERNAL ERROR at %s:%d"), __FILE__, __LINE__);
- abort();
-
- case grecs_type_section:
- grecs_error(locus, 0,
- _("invalid use of block statement"));
- return 1;
- }
- return 0;
-}
-
-struct grecs_prop
-{
- size_t size;
- int (*cmp)(const void *, const void *);
-};
-
-static int
-string_cmp(const void *elt1, const void *elt2)
-{
- return strcmp((const char *)elt1,(const char *)elt2);
-}
-
-#define __grecs_name_cat__(a,b) a ## b
-#define NUMCMP(type) __grecs_name_cat__(type,_cmp)
-#define __DECL_NUMCMP(type,ctype) \
- static int \
- NUMCMP(type)(const void *elt1, const void *elt2) \
- { \
- return memcmp(elt1, elt2, sizeof(ctype)); \
- }
-#define DECL_NUMCMP(type) __DECL_NUMCMP(type,type)
-
-DECL_NUMCMP(short)
-DECL_NUMCMP(int)
-DECL_NUMCMP(long)
-DECL_NUMCMP(size_t)
-/*FIXME DECL_NUMCMP(off_t)*/
-DECL_NUMCMP(time_t)
-__DECL_NUMCMP(in_addr, struct in_addr)
-__DECL_NUMCMP(grecs_sockaddr, struct grecs_sockaddr)
-
-struct grecs_prop grecs_prop_tab[] = {
- { 0, NULL }, /* grecs_type_void */
- { sizeof(char*), string_cmp }, /* grecs_type_string */
- { sizeof(short), NUMCMP(short) }, /* grecs_type_short */
- { sizeof(unsigned short), NUMCMP(short) }, /* grecs_type_ushort */
- { sizeof(int), NUMCMP(int) }, /* grecs_type_int */
- { sizeof(unsigned int), NUMCMP(int) }, /* grecs_type_uint */
- { sizeof(long), NUMCMP(long) }, /* grecs_type_long */
- { sizeof(unsigned long), NUMCMP(long) }, /* grecs_type_ulong */
- { sizeof(size_t), NUMCMP(size_t) }, /* grecs_type_size */
-#if 0
- FIXME
- { sizeof(off_t), NUMCMP(off_t) }, /* grecs_type_off */
-#endif
- { sizeof(time_t), NUMCMP(time_t) }, /* grecs_type_time */
- { sizeof(int), NUMCMP(int) }, /* grecs_type_bool */
- { sizeof(struct in_addr), NUMCMP(in_addr) }, /* grecs_type_ipv4 */
- { 0, NULL }, /* FIXME: grecs_type_cidr */
- { sizeof(struct in_addr), NUMCMP(in_addr) }, /* grecs_type_host */
- { sizeof(struct grecs_sockaddr), NUMCMP(grecs_sockaddr) },
- /* grecs_type_sockaddr */
- { 0, NULL } /* grecs_type_section */
-};
-#define grecs_prop_count \
- (sizeof(grecs_prop_tab) / sizeof(grecs_prop_tab[0]))
-
-void
-grecs_process_ident(struct grecs_keyword *kwp, grecs_value_t *value,
- void *base, grecs_locus_t *locus)
-{
- void *target;
-
- if (!kwp)
- return;
-
- target = target_ptr(kwp, (char *) base);
-
- if (kwp->callback)
- kwp->callback(grecs_callback_set_value,
- locus,
- target,
- value,
- &kwp->callback_data);
- else if (value->type == GRECS_TYPE_ARRAY) {
- grecs_error(locus, 0,
- _("too many arguments to `%s'; missing semicolon?"),
- kwp->ident);
- return;
- } else if (value->type == GRECS_TYPE_LIST) {
- if (GRECS_IS_LIST(kwp->type)) {
- struct grecs_list_entry *ep;
- enum grecs_data_type type = GRECS_TYPE(kwp->type);
- int num = 1;
- struct grecs_list *list;
- size_t size;
-
- if (type >= grecs_prop_count
- || (size = grecs_prop_tab[type].size) == 0) {
- grecs_error(locus, 0,
- _("INTERNAL ERROR at %s:%d: "
- "unhandled data type %d"),
- __FILE__, __LINE__, type);
- abort();
- }
-
- list = grecs_list_create();
- list->cmp = grecs_prop_tab[type].cmp;
-
- for (ep = value->v.list->head; ep; ep = ep->next) {
- const grecs_value_t *vp = ep->data;
-
- if (vp->type != GRECS_TYPE_STRING)
- grecs_error(locus, 0,
- _("%s: incompatible data type in list item #%d"),
- kwp->ident, num);
- else if (type == grecs_type_string)
- grecs_list_append(list,
- (void*) vp->v.string);
- else {
- void *ptr = grecs_malloc(size);
- if (grecs_string_convert(ptr,
- type,
- vp->v.string,
- locus) == 0)
- grecs_list_append(list, ptr);
- else
- free(ptr);
- }
- }
- *(struct grecs_list**)target = list;
- } else {
- grecs_error(locus, 0,
- _("incompatible data type for `%s'"),
- kwp->ident);
- return;
- }
- } else if (GRECS_IS_LIST(kwp->type)) {
- struct grecs_list *list;
- enum grecs_data_type type = GRECS_TYPE(kwp->type);
- size_t size;
- void *ptr;
-
- if (type >= grecs_prop_count
- || (size = grecs_prop_tab[type].size) == 0) {
- grecs_error(locus, 0,
- _("INTERNAL ERROR at %s:%d: unhandled data type %d"),
- __FILE__, __LINE__, type);
- abort();
- }
-
- list = grecs_list_create();
- list->cmp = grecs_prop_tab[type].cmp;
- list->free_entry = listel_dispose;
- if (type == grecs_type_string)
- grecs_list_append(list, value->v.string);
- else {
- ptr = grecs_malloc(size);
- if (grecs_string_convert(ptr, type, value->v.string, locus)) {
- free(ptr);
- grecs_list_free(list);
- return;
- }
- grecs_list_append(list, ptr);
- }
- *(struct grecs_list**)target = list;
- } else
- grecs_string_convert(target, GRECS_TYPE(kwp->type),
- value->v.string,
- locus);
-}
-
-static void
-process_ident(struct grecs_keyword *kwp, grecs_value_t *value)
-{
- grecs_process_ident(kwp, value, CURRENT_BASE, &grecs_current_locus);
-}
diff --git a/src/grecs.h b/src/grecs.h
index 5769736..e0f0fa5 100644
--- a/src/grecs.h
+++ b/src/grecs.h
@@ -100,6 +100,25 @@ typedef struct grecs_value {
} v;
} grecs_value_t;
+#define GRECS_VALUE_EMPTY_P(val) \
+ (!(val) || \
+ ((val)->type == GRECS_TYPE_STRING && (val)->v.string == NULL))
+
+enum grecs_node_type {
+ grecs_node_stmt,
+ grecs_node_block
+};
+
+typedef struct grecs_node {
+ enum grecs_node_type type;
+ grecs_locus_t locus;
+ struct grecs_node *up;
+ struct grecs_node *down;
+ struct grecs_node *next;
+ char *ident;
+ struct grecs_value value;
+} grecs_node_t;
+
typedef int (*grecs_callback_fn)(
enum grecs_callback_command cmd,
grecs_locus_t * /* locus */,
@@ -138,19 +157,20 @@ char *grecs_strdup(const char *str);
grecs_value_t *grecs_value_dup(grecs_value_t *input);
-extern void grecs_print_diag(grecs_locus_t *, int, int, const char*);
+extern void (*grecs_print_diag_fun)(grecs_locus_t *, int, int, const char*);
void grecs_warning(grecs_locus_t *locus, int errcode, const char *fmt, ...)
__attribute__ ((__format__ (__printf__, 3, 4)));
void grecs_error(grecs_locus_t *locus, int errcode, const char *fmt, ...)
__attribute__ ((__format__ (__printf__, 3, 4)));
-void grecs_set_keywords(struct grecs_keyword *kwd);
void grecs_gram_trace(int n);
void grecs_lex_trace(int n);
int grecs_lex_begin(const char*);
void grecs_lex_end(void);
-int grecs_parse(const char *name);
+struct grecs_node *grecs_parse(const char *name);
+
+struct grecs_list *_grecs_simple_list_create(int dispose);
void grecs_line_begin(void);
void grecs_line_add(const char *text, size_t len);
@@ -163,6 +183,12 @@ extern void grecs_process_ident(struct grecs_keyword *kwp,
void *base,
grecs_locus_t *locus);
+struct grecs_node *grecs_node_create(enum grecs_node_type type,
+ grecs_locus_t *loc);
+void grecs_node_bind(struct grecs_node *master, struct grecs_node *node,
+ int dn);
+
+
extern grecs_locus_t grecs_current_locus;
extern int grecs_error_count;
extern int grecs_default_port;
@@ -187,16 +213,22 @@ void grecs_include_path_setup(const char *dir, ...);
void grecs_include_path_setup_v(char **dirs);
const char *grecs_data_type_string(enum grecs_data_type type);
-void grecs_format_docstring(FILE *stream, const char *docstring,
- unsigned level);
-void grecs_format_simple_statement(FILE *stream, struct grecs_keyword *kwp,
- unsigned level);
-void grecs_format_block_statement(FILE *stream, struct grecs_keyword *kwp,
- unsigned level);
-void grecs_format_statement_array(FILE *stream, struct grecs_keyword *kwp,
- unsigned n,
- unsigned level);
-
+void grecs_format_docstring(const char *docstring, unsigned level,
+ FILE *stream);
+void grecs_format_simple_statement(struct grecs_keyword *kwp,
+ unsigned level, FILE *stream);
+void grecs_format_block_statement(struct grecs_keyword *kwp,
+ unsigned level, FILE *stream);
+void grecs_format_statement_array(struct grecs_keyword *kwp,
+ unsigned n,
+ unsigned level, FILE *stream);
+
+void grecs_format_locus(grecs_locus_t *locus, FILE *fp);
+void grecs_format_node_ident(struct grecs_node *node, int delim, FILE *fp);
+void grecs_format_value(struct grecs_value *val, FILE *fp);
+
+#define GRECS_NODE_FLAG_LOCUS 0x0100
+void grecs_format_node(struct grecs_node *node, int flags, FILE *fp);
struct grecs_list *grecs_list_create(void);
size_t grecs_list_size(struct grecs_list *lp);
@@ -240,5 +272,33 @@ int grecs_symtab_enumerate(struct grecs_symtab *st,
grecs_symtab_enumerator_t fun, void *data);
size_t grecs_symtab_count_entries(struct grecs_symtab *st);
+
+void grecs_node_free(struct grecs_node *node);
+void grecs_tree_free(struct grecs_node *node);
+
+enum grecs_tree_recurse_op {
+ grecs_tree_recurse_set,
+ grecs_tree_recurse_pre,
+ grecs_tree_recurse_post
+};
+
+enum grecs_tree_recurse_res {
+ grecs_tree_recurse_ok,
+ grecs_tree_recurse_fail,
+ grecs_tree_recurse_skip,
+ grecs_tree_recurse_stop
+};
+
+typedef enum grecs_tree_recurse_res
+ (*grecs_tree_recursor_t)(enum grecs_tree_recurse_op,
+ struct grecs_node *, void *);
+
+int grecs_tree_recurse(struct grecs_node *node, grecs_tree_recursor_t recfun,
+ void *data);
+
+int grecs_tree_process(struct grecs_node *node, struct grecs_keyword *kwd);
+
+int grecs_value_eq(struct grecs_value *a, struct grecs_value *b);
+struct grecs_node *grecs_find_node(struct grecs_node *node, const char *path);
diff --git a/src/lookup.c b/src/lookup.c
new file mode 100644
index 0000000..fdf1ae1
--- a/dev/null
+++ b/src/lookup.c
@@ -0,0 +1,272 @@
+/* grecs - Gray's Extensible Configuration System
+ Copyright (C) 2007-2011 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include "grecs.h"
+#include "wordsplit.h"
+
+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->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
+split_cfg_path(const char *path, int *pargc, char ***pargv)
+{
+ int argc;
+ char **argv;
+ char *delim = ".";
+ char static_delim[2] = { 0, 0 };
+
+ if (path[0] == '\\') {
+ argv = calloc(2, sizeof (*argv));
+ if (!argv)
+ return ENOMEM;
+ argv[0] = strdup(path + 1);
+ if (!argv[0]) {
+ free(argv);
+ return ENOMEM;
+ }
+ argv[1] = NULL;
+ argc = 1;
+ } else {
+ struct wordsplit ws;
+
+ if (ispunct(path[0])) {
+ delim = static_delim;
+ delim[0] = path[0];
+ path++;
+ }
+ ws.ws_delim = delim;
+
+ if (wordsplit(path, &ws, WRDSF_DEFFLAGS|WRDSF_DELIM))
+ return errno;
+ argc = ws.ws_wordc;
+ argv = ws.ws_wordv;
+ ws.ws_wordc = 0;
+ ws.ws_wordv = NULL;
+ wordsplit_free(&ws);
+ }
+
+ *pargc = argc;
+ *pargv = argv;
+
+ return 0;
+}
+
+static void
+free_value_mem(struct grecs_value *p)
+{
+ switch (p->type) {
+ case GRECS_TYPE_STRING:
+ free((char*)p->v.string);
+ break;
+
+ case GRECS_TYPE_LIST:
+ /* FIXME */
+ break;
+
+ case GRECS_TYPE_ARRAY: {
+ size_t i;
+ for (i = 0; i < p->v.arg.c; i++)
+ free_value_mem(&p->v.arg.v[i]);
+ }
+ }
+}
+
+static void
+destroy_value(void *p)
+{
+ struct grecs_value*val = p;
+ if (val) {
+ free_value_mem(val);
+ free(val);
+ }
+}
+
+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_list_create();
+ lst->free_entry = destroy_value;
+ for (i = 0; i < ws.ws_wordc; i++) {
+ struct grecs_value *p = grecs_malloc(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_malloc(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_malloc(ws.ws_wordc *
+ sizeof(val->v.arg.v[0]));
+ for (i = 0; i < ws.ws_wordc; i++) {
+ 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;
+}
+
+
+struct find_closure {
+ int argc;
+ char **argv;
+ int tag;
+ struct grecs_value *label;
+ struct grecs_node *node;
+};
+
+static void
+parse_tag(struct find_closure *fptr)
+{
+ char *p = strchr(fptr->argv[fptr->tag], '=');
+ if (p) {
+ *p++ = 0;
+ fptr->label = parse_label(p);
+ }
+ else
+ fptr->label = NULL;
+}
+
+static enum grecs_tree_recurse_res
+node_finder(enum grecs_tree_recurse_op op, struct grecs_node *node, void *data)
+{
+ struct find_closure *fdptr = data;
+
+ if (op == grecs_tree_recurse_post)
+ return grecs_tree_recurse_ok;
+
+ if (strcmp(fdptr->argv[fdptr->tag], node->ident) == 0
+ && (!fdptr->label || grecs_value_eq(fdptr->label, &node->value))) {
+ fdptr->tag++;
+ if (fdptr->tag == fdptr->argc) {
+ fdptr->node = node;
+ return grecs_tree_recurse_stop;
+ }
+ parse_tag(fdptr);
+ 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, i;
+ struct find_closure clos;
+
+ rc = split_cfg_path(path, &clos.argc, &clos.argv);
+ if (!clos.argc)
+ return NULL;
+ clos.tag = 0;
+ clos.label = NULL;
+ clos.node = NULL;
+ parse_tag(&clos);
+ grecs_tree_recurse(node, node_finder, &clos);
+ for (i = 0; i < clos.argc; i++)
+ free(clos.argv[i]);
+ free(clos.argv);
+ destroy_value(clos.label);
+ return clos.node;
+}
+
diff --git a/src/tree.c b/src/tree.c
new file mode 100644
index 0000000..52d38c0
--- a/dev/null
+++ b/src/tree.c
@@ -0,0 +1,773 @@
+/* grecs - Gray's Extensible Configuration System
+ Copyright (C) 2007-2011 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/time.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdlib.h>
+#include "grecs.h"
+
+struct grecs_node *
+grecs_node_create(enum grecs_node_type type, grecs_locus_t *loc)
+{
+ struct grecs_node *np = grecs_zalloc(sizeof(*np));
+ np->type = type;
+ if (loc)
+ np->locus = *loc;
+ return np;
+}
+
+void
+grecs_node_bind(struct grecs_node *master, struct grecs_node *node, int dn)
+{
+ struct grecs_node *np;
+
+ if (dn) {
+ if (!master->down)
+ master->down = node;
+ else {
+ for (np = master->down; np->next; np = np->next)
+ ;
+ np->next = node;
+ }
+ for (; node; node = node->next)
+ node->up = master;
+ } else {
+ if (!master->next)
+ master->next = node;
+ else {
+ for (np = master->next; np->next; np = np->next)
+ ;
+ np->next = node;
+ }
+ node->up = master->up;
+ }
+}
+
+
+static enum grecs_tree_recurse_res
+_tree_recurse(struct grecs_node *node, grecs_tree_recursor_t recfun,
+ void *data)
+{
+ enum grecs_tree_recurse_res res;
+#define CKRES() \
+ switch (res) { \
+ case grecs_tree_recurse_fail: \
+ case grecs_tree_recurse_stop: \
+ return res; \
+ default: \
+ break; \
+ }
+
+ for (; node; node = node->next) {
+ if (node->type == grecs_node_stmt) {
+ res = recfun(grecs_tree_recurse_set, node, data);
+ CKRES();
+ } else {
+ switch (recfun(grecs_tree_recurse_pre, node, data)) {
+ case grecs_tree_recurse_ok:
+ res = _tree_recurse(node->down, recfun, data);
+ CKRES();
+ res = recfun(grecs_tree_recurse_post, node,
+ data);
+ CKRES();
+ break;
+ case grecs_tree_recurse_fail:
+ return grecs_tree_recurse_fail;
+ case grecs_tree_recurse_stop:
+ return grecs_tree_recurse_stop;
+ case grecs_tree_recurse_skip:
+ break;
+ }
+ }
+ }
+ return grecs_tree_recurse_ok;
+#undef CKRES
+}
+
+int
+grecs_tree_recurse(struct grecs_node *node, grecs_tree_recursor_t recfun,
+ void *data)
+{
+ switch (_tree_recurse(node, recfun, data)) {
+ case grecs_tree_recurse_ok:
+ case grecs_tree_recurse_stop:
+ return 0;
+ default:
+ break;
+ }
+ return 1;
+}
+
+void
+grecs_node_free(struct grecs_node *node)
+{
+ /*FIXME: value*/
+ free(node);
+}
+
+void
+grecs_tree_free(struct grecs_node *node)
+{
+ while (node) {
+ struct grecs_node *next = node->next;
+ if (node->down)
+ grecs_tree_free(node->down);
+ grecs_node_free(node);
+ node = next;
+ }
+}
+
+
+
+static int
+fake_callback(enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr,
+ grecs_value_t *value,
+ void *cb_data)
+{
+ return 0;
+}
+
+static struct grecs_keyword fake = {
+ "*",
+ NULL,
+ NULL,
+ grecs_type_void,
+ NULL,
+ 0,
+ fake_callback,
+ NULL,
+ &fake
+};
+
+static struct grecs_keyword *
+find_keyword(struct grecs_keyword *cursect, const char *ident)
+{
+ struct grecs_keyword *kwp;
+
+ if (cursect && cursect != &fake) {
+ for (kwp = cursect->kwd; kwp->ident; kwp++)
+ if (strcmp(kwp->ident, ident) == 0)
+ return kwp;
+ } else {
+ return &fake;
+ }
+ return NULL;
+}
+
+static void *
+target_ptr(struct grecs_keyword *kwp, char *base)
+{
+ if (kwp->varptr)
+ base = (char*) kwp->varptr + kwp->offset;
+ else if (base)
+ base += kwp->offset;
+
+ return base;
+}
+
+static int
+string_to_bool(const char *string, int *pval, grecs_locus_t *locus)
+{
+ if (strcmp(string, "yes") == 0
+ || strcmp(string, "true") == 0
+ || strcmp(string, "t") == 0
+ || strcmp(string, "1") == 0)
+ *pval = 1;
+ else if (strcmp(string, "no") == 0
+ || strcmp(string, "false") == 0
+ || strcmp(string, "nil") == 0
+ || strcmp(string, "0") == 0)
+ *pval = 0;
+ else {
+ grecs_error(locus, 0,
+ _("%s: not a valid boolean value"),
+ string);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+string_to_host(struct in_addr *in, const char *string, grecs_locus_t *locus)
+{
+ if (inet_aton(string, in) == 0) {
+ struct hostent *hp;
+
+ hp = gethostbyname(string);
+ if (hp == NULL)
+ return 1;
+ memcpy(in, hp->h_addr, sizeof(struct in_addr));
+ }
+ return 0;
+}
+
+static int
+string_to_sockaddr(struct grecs_sockaddr *sp, const char *string,
+ grecs_locus_t *locus)
+{
+ if (string[0] == '/') {
+ struct sockaddr_un s_un;
+ if (strlen(string) >= sizeof(s_un.sun_path)) {
+ grecs_error(locus, 0,
+ _("%s: UNIX socket name too long"),
+ string);
+ return 1;
+ }
+ s_un.sun_family = AF_UNIX;
+ strcpy(s_un.sun_path, string);
+ sp->len = sizeof(s_un);
+ sp->sa = grecs_malloc(sp->len);
+ memcpy(sp->sa, &s_un, sp->len);
+ } else {
+ char *p = strchr(string, ':');
+ size_t len;
+ struct sockaddr_in sa;
+
+ sa.sin_family = AF_INET;
+ if (p)
+ len = p - string;
+ else
+ len = strlen(string);
+
+ if (len == 0)
+ sa.sin_addr.s_addr = INADDR_ANY;
+ else {
+ char *host = grecs_malloc(len + 1);
+ memcpy(host, string, len);
+ host[len] = 0;
+
+ if (string_to_host(&sa.sin_addr, host, locus)) {
+ grecs_error(locus, 0,
+ _("%s: not a valid IP address or hostname"),
+ host);
+ free(host);
+ return 1;
+ }
+ free(host);
+ }
+
+ if (p) {
+ struct servent *serv;
+
+ p++;
+ serv = getservbyname(p, "tcp");
+ if (serv != NULL)
+ sa.sin_port = serv->s_port;
+ else {
+ unsigned long l;
+ char *q;
+
+ /* Not in services, maybe a number? */
+ l = strtoul(p, &q, 0);
+
+ if (*q || l > USHRT_MAX) {
+ grecs_error(locus, 0,
+ _("%s: not a valid port number"), p);
+ return 1;
+ }
+ sa.sin_port = htons(l);
+ }
+ } else if (grecs_default_port)
+ sa.sin_port = grecs_default_port;
+ else {
+ grecs_error(locus, 0, _("missing port number"));
+ return 1;
+ }
+ sp->len = sizeof(sa);
+ sp->sa = grecs_malloc(sp->len);
+ memcpy(sp->sa, &sa, sp->len);
+ }
+ return 0;
+}
+
+
+/* The TYPE_* defines come from gnulib's intprops.h */
+
+/* True if the arithmetic type T is signed. */
+# define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+
+/* The maximum and minimum values for the integer type T. These
+ macros have undefined behavior if T is signed and has padding bits. */
+# define TYPE_MINIMUM(t) \
+ ((t) (! TYPE_SIGNED(t) \
+ ? (t) 0 \
+ : TYPE_SIGNED_MAGNITUDE(t) \
+ ? ~ (t) 0 \
+ : ~ TYPE_MAXIMUM(t)))
+# define TYPE_MAXIMUM(t) \
+ ((t) (! TYPE_SIGNED(t) \
+ ? (t) -1 \
+ : ((((t) 1 << (sizeof(t) * CHAR_BIT - 2)) - 1) * 2 + 1)))
+# define TYPE_SIGNED_MAGNITUDE(t) ((t) ~ (t) 0 < (t) -1)
+
+
+#define STRTONUM(s, type, base, res, limit, loc) \
+ { \
+ type sum = 0; \
+ \
+ for (; *s; s++) \
+ { \
+ type x; \
+ \
+ if ('0' <= *s && *s <= '9') \
+ x = sum * base + *s - '0'; \
+ else if (base == 16 && 'a' <= *s && *s <= 'f') \
+ x = sum * base + *s - 'a'; \
+ else if (base == 16 && 'A' <= *s && *s <= 'F') \
+ x = sum * base + *s - 'A'; \
+ else \
+ break; \
+ if (x <= sum) \
+ { \
+ grecs_error(loc, 0, _("numeric overflow")); \
+ return 1; \
+ } \
+ else if (limit && x > limit) \
+ { \
+ grecs_error(loc, 0, _("value out of allowed range")); \
+ return 1; \
+ } \
+ sum = x; \
+ } \
+ res = sum; \
+ }
+
+#define STRxTONUM(s, type, res, limit, loc) \
+ { \
+ int base; \
+ if (*s == '0') \
+ { \
+ s++; \
+ if (*s == 0) \
+ base = 10; \
+ else if (*s == 'x' || *s == 'X') \
+ { \
+ s++; \
+ base = 16; \
+ } \
+ else \
+ base = 8; \
+ } else \
+ base = 10; \
+ STRTONUM(s, type, base, res, limit, loc); \
+ }
+
+#define GETUNUM(str, type, res, loc) \
+ { \
+ type tmpres; \
+ const char *s = str; \
+ STRxTONUM(s, type, tmpres, 0, loc); \
+ if (*s) \
+ { \
+ grecs_error(loc, 0, _("not a number (stopped near `%s')"), \
+ s); \
+ return 1; \
+ } \
+ res = tmpres; \
+ }
+
+#define GETSNUM(str, type, res, loc) \
+ { \
+ unsigned type tmpres; \
+ const char *s = str; \
+ int sign; \
+ unsigned type limit; \
+ \
+ if (*s == '-') \
+ { \
+ sign = 1; \
+ s++; \
+ limit = TYPE_MINIMUM(type); \
+ limit = - limit; \
+ } \
+ else \
+ { \
+ sign = 0; \
+ limit = TYPE_MAXIMUM(type); \
+ } \
+ \
+ STRxTONUM(s, unsigned type, tmpres, limit, loc); \
+ if (*s) \
+ { \
+ grecs_error(loc, 0, _("not a number (stopped near `%s')"), s); \
+ return 1; \
+ } \
+ res = sign ? - tmpres : tmpres; \
+ }
+
+
+int
+grecs_string_convert(void *target, enum grecs_data_type type,
+ const char *string, grecs_locus_t *locus)
+{
+ switch (type) {
+ case grecs_type_void:
+ abort();
+
+ case grecs_type_string:
+ *(const char**)target = string;
+ break;
+
+ case grecs_type_short:
+ GETUNUM(string, short, *(short*)target, locus);
+ break;
+
+ case grecs_type_ushort:
+ GETUNUM(string, unsigned short, *(unsigned short*)target, locus);
+ break;
+
+ case grecs_type_bool:
+ return string_to_bool(string, (int*)target, locus);
+
+ case grecs_type_int:
+ GETSNUM(string, int, *(int*)target, locus);
+ break;
+
+ case grecs_type_uint:
+ GETUNUM(string, unsigned int, *(unsigned int*)target, locus);
+ break;
+
+ case grecs_type_long:
+ GETSNUM(string, long, *(long*)target, locus);
+ break;
+
+ case grecs_type_ulong:
+ GETUNUM(string, unsigned long, *(unsigned long*)target, locus);
+ break;
+
+ case grecs_type_size:
+ GETUNUM(string, size_t, *(size_t*)target, locus);
+ break;
+ /*FIXME
+ case grecs_type_off:
+ GETSNUM(string, off_t, *(off_t*)target, locus);
+ break;
+ */
+ case grecs_type_time:
+ /*FIXME: Use getdate */
+ GETUNUM(string, time_t, *(time_t*)target, locus);
+ break;
+
+ case grecs_type_ipv4:
+ if (inet_aton(string, (struct in_addr *)target)) {
+ grecs_error(locus, 0, _("%s: not a valid IP address"),
+ string);
+ return 1;
+ }
+ break;
+
+ case grecs_type_host:
+ if (string_to_host((struct in_addr *)target, string, locus)) {
+ grecs_error(locus, 0,
+ _("%s: not a valid IP address or hostname"),
+ string);
+ return 1;
+ }
+ break;
+
+ case grecs_type_sockaddr:
+ return string_to_sockaddr((struct grecs_sockaddr *)target, string,
+ locus);
+
+ /* FIXME: */
+ case grecs_type_cidr:
+ grecs_error(locus, 0,
+ _("INTERNAL ERROR at %s:%d"), __FILE__, __LINE__);
+ abort();
+
+ case grecs_type_section:
+ grecs_error(locus, 0,
+ _("invalid use of block statement"));
+ return 1;
+ }
+ return 0;
+}
+
+struct grecs_prop
+{
+ size_t size;
+ int (*cmp)(const void *, const void *);
+};
+
+static int
+string_cmp(const void *elt1, const void *elt2)
+{
+ return strcmp((const char *)elt1,(const char *)elt2);
+}
+
+#define __grecs_name_cat__(a,b) a ## b
+#define NUMCMP(type) __grecs_name_cat__(type,_cmp)
+#define __DECL_NUMCMP(type,ctype) \
+ static int \
+ NUMCMP(type)(const void *elt1, const void *elt2) \
+ { \
+ return memcmp(elt1, elt2, sizeof(ctype)); \
+ }
+#define DECL_NUMCMP(type) __DECL_NUMCMP(type,type)
+
+DECL_NUMCMP(short)
+DECL_NUMCMP(int)
+DECL_NUMCMP(long)
+DECL_NUMCMP(size_t)
+/*FIXME DECL_NUMCMP(off_t)*/
+DECL_NUMCMP(time_t)
+__DECL_NUMCMP(in_addr, struct in_addr)
+__DECL_NUMCMP(grecs_sockaddr, struct grecs_sockaddr)
+
+struct grecs_prop grecs_prop_tab[] = {
+ { 0, NULL }, /* grecs_type_void */
+ { sizeof(char*), string_cmp }, /* grecs_type_string */
+ { sizeof(short), NUMCMP(short) }, /* grecs_type_short */
+ { sizeof(unsigned short), NUMCMP(short) }, /* grecs_type_ushort */
+ { sizeof(int), NUMCMP(int) }, /* grecs_type_int */
+ { sizeof(unsigned int), NUMCMP(int) }, /* grecs_type_uint */
+ { sizeof(long), NUMCMP(long) }, /* grecs_type_long */
+ { sizeof(unsigned long), NUMCMP(long) }, /* grecs_type_ulong */
+ { sizeof(size_t), NUMCMP(size_t) }, /* grecs_type_size */
+#if 0
+ FIXME
+ { sizeof(off_t), NUMCMP(off_t) }, /* grecs_type_off */
+#endif
+ { sizeof(time_t), NUMCMP(time_t) }, /* grecs_type_time */
+ { sizeof(int), NUMCMP(int) }, /* grecs_type_bool */
+ { sizeof(struct in_addr), NUMCMP(in_addr) }, /* grecs_type_ipv4 */
+ { 0, NULL }, /* FIXME: grecs_type_cidr */
+ { sizeof(struct in_addr), NUMCMP(in_addr) }, /* grecs_type_host */
+ { sizeof(struct grecs_sockaddr), NUMCMP(grecs_sockaddr) },
+ /* grecs_type_sockaddr */
+ { 0, NULL } /* grecs_type_section */
+};
+#define grecs_prop_count \
+ (sizeof(grecs_prop_tab) / sizeof(grecs_prop_tab[0]))
+
+void
+grecs_process_ident(struct grecs_keyword *kwp, grecs_value_t *value,
+ void *base, grecs_locus_t *locus)
+{
+ void *target;
+
+ if (!kwp)
+ return;
+
+ target = target_ptr(kwp, (char *) base);
+
+ if (kwp->callback)
+ kwp->callback(grecs_callback_set_value,
+ locus,
+ target,
+ value,
+ &kwp->callback_data);
+ else if (value->type == GRECS_TYPE_ARRAY) {
+ grecs_error(locus, 0,
+ _("too many arguments to `%s'; missing semicolon?"),
+ kwp->ident);
+ return;
+ } else if (value->type == GRECS_TYPE_LIST) {
+ if (GRECS_IS_LIST(kwp->type)) {
+ struct grecs_list_entry *ep;
+ enum grecs_data_type type = GRECS_TYPE(kwp->type);
+ int num = 1;
+ struct grecs_list *list;
+ size_t size;
+
+ if (type >= grecs_prop_count
+ || (size = grecs_prop_tab[type].size) == 0) {
+ grecs_error(locus, 0,
+ _("INTERNAL ERROR at %s:%d: "
+ "unhandled data type %d"),
+ __FILE__, __LINE__, type);
+ abort();
+ }
+
+ list = grecs_list_create();
+ list->cmp = grecs_prop_tab[type].cmp;
+
+ for (ep = value->v.list->head; ep; ep = ep->next) {
+ const grecs_value_t *vp = ep->data;
+
+ if (vp->type != GRECS_TYPE_STRING)
+ grecs_error(locus, 0,
+ _("%s: incompatible data type in list item #%d"),
+ kwp->ident, num);
+ else if (type == grecs_type_string)
+ grecs_list_append(list,
+ (void*) vp->v.string);
+ else {
+ void *ptr = grecs_malloc(size);
+ if (grecs_string_convert(ptr,
+ type,
+ vp->v.string,
+ locus) == 0)
+ grecs_list_append(list, ptr);
+ else
+ free(ptr);
+ }
+ }
+ *(struct grecs_list**)target = list;
+ } else {
+ grecs_error(locus, 0,
+ _("incompatible data type for `%s'"),
+ kwp->ident);
+ return;
+ }
+ } else if (GRECS_IS_LIST(kwp->type)) {
+ struct grecs_list *list;
+ enum grecs_data_type type = GRECS_TYPE(kwp->type);
+ size_t size;
+ void *ptr;
+
+ if (type >= grecs_prop_count
+ || (size = grecs_prop_tab[type].size) == 0) {
+ grecs_error(locus, 0,
+ _("INTERNAL ERROR at %s:%d: unhandled data type %d"),
+ __FILE__, __LINE__, type);
+ abort();
+ }
+
+ list = _grecs_simple_list_create(1);
+ list->cmp = grecs_prop_tab[type].cmp;
+ if (type == grecs_type_string)
+ grecs_list_append(list, value->v.string);
+ else {
+ ptr = grecs_malloc(size);
+ if (grecs_string_convert(ptr, type, value->v.string, locus)) {
+ free(ptr);
+ grecs_list_free(list);
+ return;
+ }
+ grecs_list_append(list, ptr);
+ }
+ *(struct grecs_list**)target = list;
+ } else
+ grecs_string_convert(target, GRECS_TYPE(kwp->type),
+ value->v.string,
+ locus);
+}
+
+
+struct nodeproc_closure {
+ struct grecs_keyword *cursect;
+ struct grecs_list *sections;
+};
+#define CURRENT_BASE(clos) \
+ ((char*)((clos)->cursect ? (clos)->cursect->callback_data : NULL))
+
+static void
+stmt_begin(struct nodeproc_closure *clos,
+ struct grecs_keyword *kwp, struct grecs_node *node)
+{
+ void *target;
+
+ grecs_list_push(clos->sections, clos->cursect);
+ if (kwp) {
+ target = target_ptr(kwp, CURRENT_BASE(clos));
+ clos->cursect = kwp;
+ if (kwp->callback) {
+ if (kwp->callback(grecs_callback_section_begin,
+ &node->locus,
+ target,
+ &node->value,
+ &kwp->callback_data))
+ clos->cursect = &fake;
+ } else
+ kwp->callback_data = target;
+ } else
+ /* install an "ignore-all" section */
+ clos->cursect = kwp;
+}
+
+static void
+stmt_end(struct nodeproc_closure *clos, struct grecs_node *node)
+{
+ grecs_callback_fn callback = NULL;
+ void *dataptr = NULL;
+ struct grecs_keyword *kwp = clos->cursect;
+
+ if (clos->cursect && clos->cursect->callback) {
+ callback = clos->cursect->callback;
+ dataptr = &clos->cursect->callback_data;
+ }
+
+ clos->cursect = (struct grecs_keyword *)grecs_list_pop(clos->sections);
+ if (!clos->cursect)
+ abort();
+ if (callback)
+ callback(grecs_callback_section_end,
+ &node->locus,
+ kwp ? target_ptr(kwp, CURRENT_BASE(clos)) : NULL,
+ NULL,
+ dataptr);
+ if (kwp)
+ kwp->callback_data = NULL;
+}
+
+static enum grecs_tree_recurse_res
+nodeproc(enum grecs_tree_recurse_op op, struct grecs_node *node, void *data)
+{
+ struct nodeproc_closure *clos = data;
+ struct grecs_keyword *kwp;
+
+ switch (op) {
+ case grecs_tree_recurse_set:
+ kwp = find_keyword(clos->cursect, node->ident);
+ if (!kwp) {
+ grecs_error(&node->locus, 0, _("Unknown keyword"));
+ return grecs_tree_recurse_skip;
+ }
+ grecs_process_ident(kwp, &node->value, CURRENT_BASE(clos),
+ &node->locus);
+ break;
+
+ case grecs_tree_recurse_pre:
+ kwp = find_keyword(clos->cursect, node->ident);
+ if (!kwp) {
+ grecs_error(&node->locus, 0, _("Unknown keyword"));
+ return grecs_tree_recurse_skip;
+ }
+ stmt_begin(clos, kwp, node);
+ break;
+
+ case grecs_tree_recurse_post:
+ stmt_end(clos, node);
+ break;
+ }
+ return grecs_tree_recurse_ok;
+}
+
+int
+grecs_tree_process(struct grecs_node *node, struct grecs_keyword *kwd)
+{
+ int rc;
+ struct nodeproc_closure clos;
+ struct grecs_keyword config_keywords;
+
+ config_keywords.kwd = kwd;
+ clos.cursect = &config_keywords;
+ clos.sections = grecs_list_create();
+ rc = grecs_tree_recurse(node, nodeproc, &clos);
+ grecs_list_free(clos.sections);
+ return rc;
+}
diff --git a/tests/.gitignore b/tests/.gitignore
new file mode 100644
index 0000000..3110c40
--- a/dev/null
+++ b/tests/.gitignore
@@ -0,0 +1,8 @@
+atconfig
+atlocal
+gcffmt
+gcfpeek
+gcfset
+package.m4
+testsuite
+testsuite.log
diff --git a/tests/Makefile.am b/tests/Makefile.am
new file mode 100644
index 0000000..4312f99
--- a/dev/null
+++ b/tests/Makefile.am
@@ -0,0 +1,84 @@
+# This file is part of grecs - Gray's Extensible Configuration System
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+EXTRA_DIST = $(TESTSUITE_AT) testsuite package.m4 gcf1.conf
+DISTCLEANFILES = atconfig $(check_SCRIPTS)
+MAINTAINERCLEANFILES = Makefile.in $(TESTSUITE)
+
+
+## ------------ ##
+## package.m4. ##
+## ------------ ##
+
+$(srcdir)/package.m4: $(top_srcdir)/configure.ac
+ $(AM_V_GEN){ \
+ echo '# Signature of the current package.'; \
+ echo 'm4_define([AT_PACKAGE_NAME], [@PACKAGE_NAME@])'; \
+ echo 'm4_define([AT_PACKAGE_TARNAME], [@PACKAGE_TARNAME@])'; \
+ echo 'm4_define([AT_PACKAGE_VERSION], [@PACKAGE_VERSION@])'; \
+ echo 'm4_define([AT_PACKAGE_STRING], [@PACKAGE_STRING@])'; \
+ echo 'm4_define([AT_PACKAGE_BUGREPORT], [@PACKAGE_BUGREPORT@])'; \
+ } >$(srcdir)/package.m4
+
+#
+
+## ------------ ##
+## Test suite. ##
+## ------------ ##
+
+TESTSUITE_AT = \
+ format00.at\
+ format01.at\
+ format02.at\
+ cfhelp.at\
+ peek00.at\
+ peek01.at\
+ peek02.at\
+ peek03.at\
+ set.at\
+ testsuite.at
+
+TESTSUITE = $(srcdir)/testsuite
+M4=m4
+
+AUTOTEST = $(AUTOM4TE) --language=autotest
+$(TESTSUITE): package.m4 $(TESTSUITE_AT)
+ $(AUTOTEST) -I $(srcdir) testsuite.at -o $@.tmp
+ mv $@.tmp $@
+
+atconfig: $(top_builddir)/config.status
+ cd $(top_builddir) && ./config.status tests/$@
+
+clean-local:
+ test ! -f $(TESTSUITE) || $(SHELL) $(TESTSUITE) --clean
+
+check-local: atconfig atlocal $(TESTSUITE)
+ $(SHELL) $(TESTSUITE)
+
+# Run the test suite on the *installed* tree.
+#installcheck-local:
+# $(SHELL) $(TESTSUITE) AUTOTEST_PATH=$(exec_prefix)/bin
+
+noinst_PROGRAMS = \
+ gcffmt\
+ gcfpeek\
+ gcfset
+
+LDADD=../src/libgrecs.a
+INCLUDES = -I$(top_srcdir)/@GRECS_SUBDIR@/src
+
+
+
diff --git a/tests/atlocal.in b/tests/atlocal.in
new file mode 100644
index 0000000..553dae3
--- a/dev/null
+++ b/tests/atlocal.in
@@ -0,0 +1,10 @@
+# @configure_input@ -*- shell-script -*-
+# Configurable variable values for Grecs test suite.
+# Copyright (C) 2011 Sergey Poznyakoff
+
+PATH=@abs_builddir@:$PATH
+
+XFAILFILE=$abs_builddir/.badversion
+
+trap "cleanup; test -r $XFAILFILE && cat $XFAILFILE; exit $?" 1 2 13 15
+
diff --git a/tests/cfhelp.at b/tests/cfhelp.at
new file mode 100644
index 0000000..cab6624
--- a/dev/null
+++ b/tests/cfhelp.at
@@ -0,0 +1,76 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP(Help)
+AT_KEYWORDS([help])
+
+AT_CHECK([gcfset -cfhelp],
+[0],
+[# Sample configuration file structure.
+
+# Scalar string
+scalar <label: string>;
+
+# Configure logging logging
+logging {
+ # Send to syslog
+ syslog <arg: boolean>;
+
+ # Set logging facility
+ facility <name: string>;
+
+ # Tag logging messages with this string
+ tag <label: string>;
+
+ # Prefix each message with its priority
+ print-priority <arg: boolean>;
+}
+
+# Mailbox configuration
+mailbox {
+ # Default mailbox pattern
+ mailbox-pattern <arg: string>;
+
+ # Default mailbox type
+ mailbox-type <arg: string>;
+}
+
+# Subprogram configuration
+program <name: string> {
+ # Scalar string
+ scalar <label: string>;
+
+ # Configure logging logging
+ logging {
+ # Send to syslog
+ syslog <arg: boolean>;
+
+ # Set logging facility
+ facility <name: string>;
+
+ # Tag logging messages with this string
+ tag <label: string>;
+
+ # Prefix each message with its priority
+ print-priority <arg: boolean>;
+ }
+}
+
+# list variable
+listvar <arg: list of string>;
+])
+
+AT_CLEANUP
diff --git a/tests/format00.at b/tests/format00.at
new file mode 100644
index 0000000..e7ae15f
--- a/dev/null
+++ b/tests/format00.at
@@ -0,0 +1,38 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP(Format)
+AT_KEYWORDS([format format00])
+
+AT_CHECK([gcffmt $abs_srcdir/gcf1.conf],
+[0],
+[.scalar: "yes"
+.listvar: ("a", "2", "b", "c")
+.compound: "stmt" "2" "foo"
+.mailbox.mailbox-pattern: "maildir:/var/mail;type=index;param=2;user=${user}"
+.mailbox.mailbox-type: "maildir"
+.logging.syslog: "yes"
+.logging.facility: "mail"
+.program="foo".logging.syslog: "yes"
+.program="foo".logging.facility: "local1"
+.program="foo".scalar: "no"
+.program="bar".logging.syslog: "no"
+.program="bar".logging.facility: "local2"
+.program="bar".logging.tag: "baz"
+.program="bar".scalar: "25"
+])
+
+AT_CLEANUP
diff --git a/tests/format01.at b/tests/format01.at
new file mode 100644
index 0000000..b8932e4
--- a/dev/null
+++ b/tests/format01.at
@@ -0,0 +1,38 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP([Format: locus])
+AT_KEYWORDS([format format01])
+
+AT_CHECK([gcffmt -locus $abs_srcdir/gcf1.conf|sed 's|^.*/gcf1.conf||'],
+[0],
+[:19: .scalar: "yes"
+:22: .listvar: ("a", "2", "b", "c")
+:25: .compound: "stmt" "2" "foo"
+:28: .mailbox.mailbox-pattern: "maildir:/var/mail;type=index;param=2;user=${user}"
+:29: .mailbox.mailbox-type: "maildir"
+:33: .logging.syslog: "yes"
+:34: .logging.facility: "mail"
+:39: .program="foo".logging.syslog: "yes"
+:40: .program="foo".logging.facility: "local1"
+:42: .program="foo".scalar: "no"
+:47: .program="bar".logging.syslog: "no"
+:48: .program="bar".logging.facility: "local2"
+:49: .program="bar".logging.tag: "baz"
+:51: .program="bar".scalar: "25"
+])
+
+AT_CLEANUP
diff --git a/tests/format02.at b/tests/format02.at
new file mode 100644
index 0000000..3ebada6
--- a/dev/null
+++ b/tests/format02.at
@@ -0,0 +1,38 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP([Format: custom delimiter])
+AT_KEYWORDS([format format02])
+
+AT_CHECK([gcffmt -delim=/ $abs_srcdir/gcf1.conf],
+[0],
+[/scalar: "yes"
+/listvar: ("a", "2", "b", "c")
+/compound: "stmt" "2" "foo"
+/mailbox/mailbox-pattern: "maildir:/var/mail;type=index;param=2;user=${user}"
+/mailbox/mailbox-type: "maildir"
+/logging/syslog: "yes"
+/logging/facility: "mail"
+/program="foo"/logging/syslog: "yes"
+/program="foo"/logging/facility: "local1"
+/program="foo"/scalar: "no"
+/program="bar"/logging/syslog: "no"
+/program="bar"/logging/facility: "local2"
+/program="bar"/logging/tag: "baz"
+/program="bar"/scalar: "25"
+])
+
+AT_CLEANUP
diff --git a/tests/gcf1.conf b/tests/gcf1.conf
new file mode 100644
index 0000000..991fb9b
--- a/dev/null
+++ b/tests/gcf1.conf
@@ -0,0 +1,54 @@
+# This file is part of grecs - Gray's Extensible Configuration System
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+/* Scalar variable.
+*/
+scalar yes;
+
+// List variable.
+listvar ("a", 2, "b", "c");
+
+# Compound value.
+compound stmt 2 foo;
+
+mailbox {
+ mailbox-pattern "maildir:/var/mail;type=index;param=2;user=${user}";
+ mailbox-type "maildir";
+};
+
+logging {
+ syslog yes;
+ facility mail;
+}
+
+program "foo" {
+ logging {
+ syslog yes;
+ facility local1;
+ }
+ scalar no;
+}
+
+program "bar" {
+ logging {
+ syslog no;
+ facility local2;
+ tag "baz";
+ }; # Note: semicolon after closing brace is optional.
+ scalar 25;
+}
+
+ \ No newline at end of file
diff --git a/tests/gcffmt.c b/tests/gcffmt.c
new file mode 100644
index 0000000..cc67b9a
--- a/dev/null
+++ b/tests/gcffmt.c
@@ -0,0 +1,67 @@
+/* grecs - Gray's Extensible Configuration System
+ Copyright (C) 2007-2011 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <string.h>
+#include <errno.h>
+#include "grecs.h"
+
+static void
+usage(const char *arg, FILE *fp, int code)
+{
+ fprintf(fp, "usage: %s [-h] [-locus] [-delim=char] file\n", arg);
+ exit(code);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *progname = argv[0];
+ char *file = NULL;
+ struct grecs_node *tree, *node;
+ int flags = 0;
+
+ while (--argc) {
+ char *arg = *++argv;
+ if (strcmp(arg, "-locus") == 0)
+ flags |= GRECS_NODE_FLAG_LOCUS;
+ else if (strncmp(arg, "-delim=", 7) == 0)
+ flags |= arg[7];
+ else if (strcmp(arg, "-h") == 0)
+ usage(progname, stdout, 0);
+ else if (arg[0] == '-')
+ usage(progname, stderr, 1);
+ else if (file)
+ usage(progname, stderr, 1);
+ else
+ file = arg;
+ }
+
+ if (!file || argc)
+ usage(progname, stderr, 1);
+
+ tree = grecs_parse(file);
+ if (!tree)
+ exit(1);
+
+ for (node = tree; node; node = node->next) {
+ grecs_format_node(node, flags, stdout);
+ fputc('\n', stdout);
+ }
+ exit(0);
+}
diff --git a/tests/gcfpeek.c b/tests/gcfpeek.c
new file mode 100644
index 0000000..360000d
--- a/dev/null
+++ b/tests/gcfpeek.c
@@ -0,0 +1,76 @@
+/* grecs - Gray's Extensible Configuration System
+ Copyright (C) 2007-2011 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <string.h>
+#include <errno.h>
+#include "grecs.h"
+
+static void
+usage(const char *arg, FILE *fp, int code)
+{
+ fprintf(fp, "usage: %s [-h] [-locus] [-delim=char] file path\n", arg);
+ exit(code);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *progname = argv[0];
+ char *path = NULL;
+ char *file = NULL;
+ struct grecs_node *tree, *node;
+ int flags = 0;
+ int rc = 2;
+
+ while (--argc) {
+ char *arg = *++argv;
+ if (strcmp(arg, "-locus") == 0)
+ flags |= GRECS_NODE_FLAG_LOCUS;
+ else if (strncmp(arg, "-delim=", 7) == 0)
+ flags |= arg[7];
+ else if (strcmp(arg, "-h") == 0)
+ usage(progname, stdout, 0);
+ else if (arg[0] == '-')
+ usage(progname, stderr, 1);
+ else if (file) {
+ if (path)
+ usage(progname, stderr, 1);
+ else
+ path = arg;
+ } else
+ file = arg;
+ }
+
+ if (!file || !path || argc)
+ usage(progname, stderr, 1);
+
+ tree = grecs_parse(file);
+ if (!tree)
+ exit(1);
+
+ for (node = tree; node; node = node->next) {
+ node = grecs_find_node(node, path);
+ if (!node)
+ break;
+ rc = 0;
+ grecs_format_node(node, flags, stdout);
+ fputc('\n', stdout);
+ }
+ exit(rc);
+}
diff --git a/tests/gcfset.c b/tests/gcfset.c
new file mode 100644
index 0000000..d2264e3
--- a/dev/null
+++ b/tests/gcfset.c
@@ -0,0 +1,240 @@
+/* grecs - Gray's Extensible Configuration System
+ Copyright (C) 2007-2011 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 <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <stddef.h>
+#include <string.h>
+#include <errno.h>
+#include "grecs.h"
+
+struct logging_setup {
+ int use_syslog;
+ int print_priority;
+ char *tag;
+ char *facility;
+};
+
+struct logging_setup logging_setup;
+char *scalar_string;
+char *mailbox_pattern;
+char *mailbox_type;
+struct grecs_list *listvar;
+
+struct program {
+ char *name;
+ struct logging_setup logging_setup;
+ char *scalar_string;
+ grecs_locus_t locus;
+ struct program *next;
+};
+
+struct grecs_list *proglist;
+
+static int
+cb_logging_facility(enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr,
+ grecs_value_t *value,
+ void *cb_data)
+{
+ if (cmd != grecs_callback_set_value) {
+ grecs_error(locus, 0, _("Unexpected block statement"));
+ return 1;
+ }
+ if (!value || value->type != GRECS_TYPE_STRING) {
+ grecs_error(locus, 0, _("expected string argument"));
+ return 1;
+ }
+
+ *(char**)varptr = grecs_strdup(value->v.string);
+ return 0;
+}
+
+static struct grecs_keyword logging_kwtab[] = {
+ { "syslog", NULL, "Send to syslog",
+ grecs_type_bool, NULL,
+ offsetof(struct logging_setup, use_syslog) },
+ { "facility", "name", "Set logging facility",
+ grecs_type_string, NULL,
+ offsetof(struct logging_setup, facility), cb_logging_facility },
+ { "tag", "label", "Tag logging messages with this string",
+ grecs_type_string, NULL,
+ offsetof(struct logging_setup, tag) },
+ { "print-priority", NULL, "Prefix each message with its priority",
+ grecs_type_bool, NULL,
+ offsetof(struct logging_setup, print_priority) },
+ { NULL },
+};
+
+static struct grecs_keyword mailbox_kwtab[] = {
+ { "mailbox-pattern", NULL, "Default mailbox pattern",
+ grecs_type_string, &mailbox_pattern },
+ { "mailbox-type", NULL, "Default mailbox type",
+ grecs_type_string, &mailbox_type },
+ { NULL },
+};
+
+
+static struct grecs_keyword program_kwtab[] = {
+ { "scalar", "label", "Scalar string",
+ grecs_type_string, NULL, offsetof(struct program,scalar_string) },
+ { "logging", NULL, N_("Configure logging logging"),
+ grecs_type_section, NULL, offsetof(struct program,logging_setup),
+ NULL, NULL, logging_kwtab },
+ { NULL }
+};
+
+static int
+cb_program(enum grecs_callback_command cmd,
+ grecs_locus_t *locus,
+ void *varptr,
+ grecs_value_t *value,
+ void *cb_data)
+{
+ struct program *prog;
+ void **pdata = cb_data;
+
+ switch (cmd) {
+ case grecs_callback_section_begin:
+ if (!value || value->type != GRECS_TYPE_STRING) {
+ grecs_error(locus, 0, _("tag must be a string"));
+ return 0;
+ }
+ prog = grecs_zalloc(sizeof(*prog));
+ prog->name = grecs_strdup(value->v.string);
+ prog->locus = *locus;
+ *pdata = prog;
+ break;
+
+ case grecs_callback_section_end:
+ prog = *pdata;
+ if (!proglist)
+ proglist = grecs_list_create();
+ grecs_list_append(proglist, prog);
+ break;
+
+ case grecs_callback_set_value:
+ grecs_error(locus, 0, _("invalid use of block statement"));
+ }
+ return 0;
+}
+
+static struct grecs_keyword main_kwtab[] = {
+ { "scalar", "label", "Scalar string",
+ grecs_type_string, &scalar_string },
+ { "logging", NULL, N_("Configure logging logging"),
+ grecs_type_section, &logging_setup, 0, NULL,
+ NULL, logging_kwtab },
+ { "mailbox", NULL, N_("Mailbox configuration"),
+ grecs_type_section, NULL, 0, NULL, NULL, mailbox_kwtab },
+ { "program", "name: string", "Subprogram configuration",
+ grecs_type_section, NULL, 0, cb_program, NULL, program_kwtab },
+ { "listvar", NULL, "list variable",
+ grecs_type_string|GRECS_LIST, &listvar },
+ { NULL }
+};
+
+
+#define S(s) ((s) ? (s) : "(null)")
+
+static void
+print_logging_setup(struct logging_setup *p)
+{
+ printf("logging: %d/%s/%s/%d\n",
+ p->use_syslog, S(p->facility), S(p->tag), p->print_priority);
+}
+
+static void
+print_program(struct program *prog)
+{
+ printf("Program %s:\n", prog->name);
+ printf("scalar = %s\n", S(prog->scalar_string));
+ print_logging_setup(&prog->logging_setup);
+}
+
+
+static void
+usage(const char *arg, FILE *fp, int code)
+{
+ fprintf(fp, "usage: %s [-h] [-cfhelp] file\n", arg);
+ exit(code);
+}
+
+int
+main(int argc, char **argv)
+{
+ char *progname = argv[0];
+ const char *file = NULL;
+ struct grecs_node *tree;
+ int cfhelp = 0;
+
+ while (--argc) {
+ char *arg = *++argv;
+ if (strcmp(arg, "-cfhelp") == 0)
+ cfhelp = 1;
+ else if (strcmp(arg, "-h") == 0)
+ usage(progname, stdout, 0);
+ else if (arg[0] == '-')
+ usage(progname, stderr, 1);
+ else if (file)
+ usage(progname, stderr, 1);
+ else
+ file = arg;
+ }
+
+ if ((!file && !cfhelp) || argc)
+ usage(progname, stderr, 1);
+
+ if (cfhelp) {
+ static char docstring[] =
+ "Sample configuration file structure.\n";
+ grecs_format_docstring(docstring, 0, stdout);
+ grecs_format_statement_array(main_kwtab, 1, 0, stdout);
+ exit(0);
+ }
+
+ tree = grecs_parse(file);
+ if (!tree)
+ exit(2);
+
+ if (grecs_tree_process(tree, main_kwtab))
+ exit(2);
+
+ printf("Global settings:\n");
+ printf("scalar = %s\n", S(scalar_string));
+ if (listvar) {
+ struct grecs_list_entry *ep;
+ printf("listvar =");
+ for (ep = listvar->head; ep; ep = ep->next)
+ printf(" \"%s\"", (char*)ep->data);
+ putchar('\n');
+ }
+
+ print_logging_setup(&logging_setup);
+
+ if (proglist) {
+ struct grecs_list_entry *ep;
+
+ printf("Programs configured: %d\n", grecs_list_size(proglist));
+ for (ep = proglist->head; ep; ep = ep->next)
+ print_program(ep->data);
+ }
+
+ exit(0);
+}
+
diff --git a/tests/peek00.at b/tests/peek00.at
new file mode 100644
index 0000000..a3d86ac
--- a/dev/null
+++ b/tests/peek00.at
@@ -0,0 +1,25 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP(Peek)
+AT_KEYWORDS([peek peek00])
+
+AT_CHECK([gcfpeek $abs_srcdir/gcf1.conf .scalar],
+[0],
+[.scalar: "yes"
+])
+
+AT_CLEANUP
diff --git a/tests/peek01.at b/tests/peek01.at
new file mode 100644
index 0000000..c76261c
--- a/dev/null
+++ b/tests/peek01.at
@@ -0,0 +1,30 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP(Peek: nested)
+AT_KEYWORDS([peek peek01])
+
+AT_CHECK([gcfpeek $abs_srcdir/gcf1.conf .program="foo".scalar],
+[0],
+[.program="foo".scalar: "no"
+])
+
+AT_CHECK([gcfpeek $abs_srcdir/gcf1.conf .program="bar".scalar],
+[0],
+[.program="bar".scalar: "25"
+])
+
+AT_CLEANUP
diff --git a/tests/peek02.at b/tests/peek02.at
new file mode 100644
index 0000000..e5c1a4a
--- a/dev/null
+++ b/tests/peek02.at
@@ -0,0 +1,26 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP(Peek: block)
+AT_KEYWORDS([peek peek02])
+
+AT_CHECK([gcfpeek $abs_srcdir/gcf1.conf .logging],
+[0],
+[.logging.syslog: "yes"
+.logging.facility: "mail"
+])
+
+AT_CLEANUP
diff --git a/tests/peek03.at b/tests/peek03.at
new file mode 100644
index 0000000..7590b0c
--- a/dev/null
+++ b/tests/peek03.at
@@ -0,0 +1,33 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP(Peek: block nested)
+AT_KEYWORDS([peek peek03])
+
+AT_CHECK([gcfpeek $abs_srcdir/gcf1.conf .program=foo.logging],
+[0],
+[.program="foo".logging.syslog: "yes"
+.program="foo".logging.facility: "local1"
+])
+
+AT_CHECK([gcfpeek $abs_srcdir/gcf1.conf .program=bar.logging],
+[0],
+[.program="bar".logging.syslog: "no"
+.program="bar".logging.facility: "local2"
+.program="bar".logging.tag: "baz"
+])
+
+AT_CLEANUP
diff --git a/tests/set.at b/tests/set.at
new file mode 100644
index 0000000..7bad831
--- a/dev/null
+++ b/tests/set.at
@@ -0,0 +1,38 @@
+# This file is part of grecs -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+AT_SETUP([Set values])
+AT_KEYWORDS([set])
+
+AT_CHECK([gcfset $abs_srcdir/gcf1.conf 2>err||exit $?
+sed 's|^.*/gcf1.conf||' err >&2],
+[0],
+[Global settings:
+scalar = yes
+listvar = "a" "2" "b" "c"
+logging: 1/mail/(null)/0
+Programs configured: 2
+Program foo:
+scalar = no
+logging: 1/local1/(null)/0
+Program bar:
+scalar = 25
+logging: 0/local2/baz/0
+],
+[:25: Unknown keyword
+])
+
+AT_CLEANUP \ No newline at end of file
diff --git a/tests/testsuite.at b/tests/testsuite.at
new file mode 100644
index 0000000..3b0ec84
--- a/dev/null
+++ b/tests/testsuite.at
@@ -0,0 +1,54 @@
+# This file is part of grecs - Gray's Extensible Configuration System -*- Autotest -*-
+# Copyright (C) 2007, 2009-2011 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, 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 <http://www.gnu.org/licenses/>.
+
+m4_version_prereq([2.52g])
+
+dnl # Standard exit codes (from sysexits.h)
+m4_define([EX_OK], 0) dnl successful termination
+m4_define([EX__BASE], 64) dnl base value for error messages
+m4_define([EX_USAGE], 64) dnl command line usage error
+m4_define([EX_DATAERR], 65) dnl data format error
+m4_define([EX_NOINPUT], 66) dnl cannot open input
+m4_define([EX_NOUSER], 67) dnl addressee unknown
+m4_define([EX_NOHOST], 68) dnl host name unknown
+m4_define([EX_UNAVAILABLE], 69) dnl service unavailable
+m4_define([EX_SOFTWARE], 70) dnl internal software error
+m4_define([EX_OSERR], 71) dnl system error (e.g., can't fork)
+m4_define([EX_OSFILE], 72) dnl critical OS file missing
+m4_define([EX_CANTCREAT], 73) dnl can't create (user) output file
+m4_define([EX_IOERR], 74) dnl input/output error
+m4_define([EX_TEMPFAIL], 75) dnl temp failure; user is invited to retry
+m4_define([EX_PROTOCOL], 76) dnl remote error in protocol
+m4_define([EX_NOPERM], 77) dnl permission denied
+m4_define([EX_CONFIG], 78) dnl configuration error
+
+m4_define([AT_SKIP_TEST],[exit 77])
+
+dnl # Begin tests
+
+AT_INIT
+m4_include([format00.at])
+m4_include([format01.at])
+m4_include([format02.at])
+
+m4_include([peek00.at])
+m4_include([peek01.at])
+m4_include([peek02.at])
+m4_include([peek03.at])
+
+m4_include([cfhelp.at])
+m4_include([set.at])
+# End of testsuite.at

Return to:

Send suggestions and report system problems to the System administrator.