/* Git-style configuration file parser for Grecs.
Copyright (C) 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 . */
#ifdef HAVE_CONFIG_H
# include
#endif
#include
#include
#include
#include
#include
static FILE *infile;
static int input_char;
static struct grecs_txtacc *acc;
#define TOK_EOF 0
#define TOK_EQ '='
#define TOK_SECTION 256
#define TOK_KEYWORD 257
#define TOK_VALUE 258
#define TOK_ERR -1
struct token {
int type;
char *buf;
char *tag;
char chbuf[2];
int putback;
} tok;
#define ISSPACE(c) (strchr(" \t\r\f\n", c) != NULL)
#define ISIDENT(c) ((isascii(c) && isalnum(c)) || (c) == '_')
#define ISINITIAL(c) ((isascii(c) && isalpha(c)) || (c) == '_')
static int
rawinput()
{
if (!infile || feof(infile))
return 0;
input_char = fgetc(infile);
if (input_char == '\n')
grecs_current_locus.line++;
return input_char;
}
static int
input()
{
rawinput();
if (input_char == '#') {
while (rawinput() && input_char != '\n')
;
}
return input_char;
}
static void
unput()
{
if (!input_char)
return;
if (input_char == '\n')
grecs_current_locus.line--;
ungetc(input_char, infile);
}
static void
error_recovery()
{
while (input() && input_char != '\n')
;
}
static void
collect_tag()
{
while (input() &&
!(ISSPACE(input_char) || input_char == ']'))
grecs_txtacc_grow_char(acc, input_char);
grecs_txtacc_grow_char(acc, 0);
tok.tag = grecs_txtacc_finish(acc, 0);
}
static void
collect_string()
{
while (rawinput()) {
if (input_char == '\\') {
if (!input()) {
grecs_error(&grecs_current_locus, 0,
"unexpected EOF in string");
break;
}
} else if (input_char == '"')
break;
grecs_txtacc_grow_char(acc, input_char);
}
grecs_txtacc_grow_char(acc, 0);
tok.tag = grecs_txtacc_finish(acc, 0);
}
static void
gettoken(void)
{
int putback = tok.putback;
tok.putback = 0;
if (putback)
return;
tok.buf = tok.tag = NULL;
/* Skip whitespace */
while (input() && ISSPACE(input_char))
;
if (input_char <= 0) {
tok.type = TOK_EOF;
return;
}
if (input_char == '[') {
tok.type = TOK_SECTION;
while (input() &&
!(ISSPACE(input_char) || input_char == ']'))
grecs_txtacc_grow_char(acc, input_char);
grecs_txtacc_grow_char(acc, 0);
tok.buf = grecs_txtacc_finish(acc, 0);
if (input_char != ']') {
while (input() && ISSPACE(input_char))
;
if (input_char == '"')
collect_string();
else if (input_char != ']')
collect_tag();
}
if (input_char != ']') {
while (input() && ISSPACE(input_char))
;
if (input_char != ']') {
if (isascii(input_char) && isprint(input_char))
grecs_error(&grecs_current_locus, 0,
"expected `]' but found `%c'",
input_char);
else if (!input_char)
grecs_error(&grecs_current_locus, 0,
"expected `]' but found EOF");
else
grecs_error(&grecs_current_locus, 0,
"expected `]' but found \\%03o",
(unsigned char)input_char);
tok.type = TOK_ERR;
}
}
return;
}
if (ISINITIAL(input_char)) {
tok.type = TOK_KEYWORD;
do
grecs_txtacc_grow_char(acc, input_char);
while (input() && ISIDENT(input_char));
unput();
grecs_txtacc_grow_char(acc, 0);
tok.buf = grecs_txtacc_finish(acc, 0);
return;
}
tok.chbuf[0] = input_char;
tok.chbuf[1] = 0;
tok.buf = tok.chbuf;
tok.type = input_char;
}
static struct grecs_value *
getvalue()
{
int len;
struct grecs_value *val = grecs_malloc(sizeof(*val));
while (input() && ISSPACE(input_char) && input_char != '\n')
;
if (input_char != '\n')
do
grecs_txtacc_grow_char(acc, input_char);
while (input() && input_char != '\n');
grecs_txtacc_grow_char(acc, 0);
tok.type = TOK_VALUE;
tok.buf = grecs_txtacc_finish(acc, 1);
len = strlen(tok.buf);
while (len > 0 && ISSPACE(tok.buf[len-1]))
tok.buf[--len] = 0;
val->type = GRECS_TYPE_STRING;
val->v.string = tok.buf;
return val;
}
static int
read_statement(struct grecs_node *parent)
{
struct grecs_node *node;
gettoken();
if (tok.type == TOK_EOF || tok.type == TOK_SECTION) {
tok.putback = 1;
return 0;
}
if (tok.type != TOK_KEYWORD) {
grecs_error(&grecs_current_locus, 0, "syntax error");
error_recovery();
return 1;
}
node = grecs_node_create(grecs_node_stmt, &grecs_current_locus);
node->ident = grecs_strdup(tok.buf);
gettoken();
if (tok.type == TOK_EOF) {
grecs_error(&grecs_current_locus, 0, "unexpected EOF");
grecs_node_free(node);
return 0;
}
if (tok.type != TOK_EQ) {
grecs_error(&grecs_current_locus, 0,
"expected `=', but found `%s'", tok.buf);
error_recovery();
grecs_node_free(node);
return 1;
}
node->v.value = getvalue();
grecs_node_bind(parent, node, 1);
return 1;
}
static void
read_statement_list(struct grecs_node *parent)
{
while (read_statement(parent))
;
}
static int
read_section(struct grecs_node *parent)
{
gettoken();
if (tok.type == TOK_EOF)
return 0;
else if (tok.type == TOK_SECTION) {
struct grecs_node *node =
grecs_node_create(grecs_node_block,
&grecs_current_locus);
node->ident = grecs_strdup(tok.buf);
if (tok.tag) {
struct grecs_value *val= grecs_malloc(sizeof(val[0]));
val->type = GRECS_TYPE_STRING;
val->v.string = grecs_strdup(tok.tag);
node->v.value = val;
}
grecs_node_bind(parent, node, 1);
read_statement_list(node);
} else if (tok.type == TOK_KEYWORD) {
read_statement(parent);
} else {
grecs_error(&grecs_current_locus, 0, "syntax error");
error_recovery();
}
return 1;
}
/* FIXME: traceflags not used */
struct grecs_node *
grecs_git_parser(const char *name, int traceflags)
{
struct grecs_node *root;
infile = fopen(name, "r");
if (!infile) {
grecs_error(NULL, errno, _("cannot open `%s'"), name);
return NULL;
}
grecs_current_locus.file = grecs_install_text(name);
grecs_current_locus.line = 1;
acc = grecs_txtacc_create();
root = grecs_node_create(grecs_node_root, &grecs_current_locus);
while (read_section(root))
;
fclose(infile);
grecs_txtacc_free(acc);
if (grecs_error_count) {
grecs_tree_free(root);
root = NULL;
}
return root;
}