/* Git-style configuration file parser for Grecs.
Copyright (C) 2011-2016 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 chbuf[2];
int putback;
struct grecs_list *path;
grecs_locus_t loc;
unsigned prev_col;
} 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 input_char = 0;
input_char = fgetc(infile);
if (input_char == '\n') {
tok.prev_col = grecs_current_locus_point.col;
grecs_locus_point_advance_line(grecs_current_locus_point);
} else if (input_char < 0)
input_char = 0;
else
grecs_current_locus_point.col++;
return input_char;
}
static int
input()
{
rawinput();
if (input_char == '#' || input_char == ';') {
while (rawinput() && input_char != '\n')
;
}
return input_char;
}
static void
unput()
{
if (!input_char)
return;
if (input_char == '\n') {
grecs_current_locus_point.line--;
grecs_current_locus_point.col = tok.prev_col;
} else
grecs_current_locus_point.col--;
ungetc(input_char, infile);
}
static void
error_recovery()
{
while (input() && input_char != '\n')
;
}
static void
collect_unquoted()
{
do
grecs_txtacc_grow_char(acc, input_char);
while (input() &&
!(ISSPACE(input_char) || input_char == ']'));
}
static void
collect_subsection_name()
{
do
grecs_txtacc_grow_char(acc, input_char);
while (input() &&
(isalnum(input_char) || input_char == '_' ||
input_char == '-'));
}
static void
collect_substring()
{
while (rawinput()) {
if (input_char == '\\') {
if (!input()) {
grecs_error(&tok.loc, 0,
"unexpected EOF in string");
break;
}
switch (input_char) {
case 'n':
input_char = '\n';
break;
case 't':
input_char = '\t';
break;
case 'b':
input_char = '\b';
}
} else if (input_char == '"')
break;
grecs_txtacc_grow_char(acc, input_char);
}
}
#define endpoint(t,adj) do { \
(t).loc.end = grecs_current_locus_point; \
if (adj) { \
if (input_char == '\n') \
(t).loc.end.col = (t).prev_col; \
else \
(t).loc.end.col -= (adj); \
} \
} while (0)
static void
gettoken(void)
{
int putback = tok.putback;
tok.putback = 0;
if (putback) {
if (putback == '\n')
grecs_locus_point_advance_line(grecs_current_locus_point);
else
grecs_current_locus_point.col++;
return;
}
tok.buf = NULL;
/* Skip whitespace */
while (input() && ISSPACE(input_char))
;
tok.loc.beg = grecs_current_locus_point;
if (input_char <= 0) {
tok.type = TOK_EOF;
endpoint(tok, 0);
return;
}
if (input_char == '[') {
int dot_delimited = -1;
tok.type = TOK_SECTION;
grecs_list_clear(tok.path);
input();
for (;;) {
char *p;
if (!dot_delimited)
while (ISSPACE(input_char))
input();
else {
if (input_char == ']')
break;
if (dot_delimited == 1)
input();
}
if (input_char == TOK_EOF) {
endpoint(tok, 0);
grecs_error(&tok.loc, 0,
"unexpected EOF in section header");
tok.type = TOK_ERR;
return;
}
if (input_char == ']')
break;
if (input_char == '\n') {
endpoint(tok, 1);
grecs_error(&tok.loc, 0,
"unexpect newline in in section header");
tok.type = TOK_ERR;
return;
}
if (dot_delimited != 1 && input_char == '"') {
collect_substring();
input();
dot_delimited = 0;
} else if (dot_delimited == 1)
collect_subsection_name();
else
collect_unquoted();
if (dot_delimited == -1)
dot_delimited = input_char == '.';
else if (dot_delimited == 1) {
if (input_char != '.' && input_char != ']') {
endpoint(tok, 1);
grecs_error(&tok.loc, 0,
"unexpected character in section header");
tok.type = TOK_ERR;
return;
}
}
grecs_txtacc_grow_char(acc, 0);
p = grecs_txtacc_finish(acc, 0);
grecs_list_append(tok.path, p);
}
endpoint(tok, 1);
if (grecs_list_size(tok.path) == 0) {
grecs_error(&tok.loc, 0, "empty section header");
tok.type = TOK_ERR;
return;
}
tok.type = TOK_SECTION;
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);
endpoint(tok, 0);
return;
}
tok.chbuf[0] = input_char;
tok.chbuf[1] = 0;
tok.buf = tok.chbuf;
tok.type = input_char;
endpoint(tok, 0);
}
static void
collect_value()
{
do {
if (input_char == '"') {
collect_substring();
if (input_char == '"')
continue;
else
break;
}
if (input_char == '\\') {
if (!rawinput())
break;
switch (input_char) {
case 'n':
input_char = '\n';
break;
case 't':
input_char = '\t';
break;
case 'b':
input_char = '\b';
}
}
grecs_txtacc_grow_char(acc, input_char);
} while (input() && input_char != '\n');
}
static struct grecs_value *
getvalue()
{
int len;
struct grecs_value *val = grecs_malloc(sizeof(*val));
while (input() && ISSPACE(input_char) && input_char != '\n')
;
val->locus.beg = grecs_current_locus_point;
if (input_char != '\n')
collect_value();
val->locus.end = grecs_current_locus_point;
val->locus.end.line--;
val->locus.end.col = tok.prev_col;
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(&tok.loc, 0, "syntax error");
error_recovery();
return 1;
}
node = grecs_node_create(grecs_node_stmt, &tok.loc);
node->ident = grecs_strdup(tok.buf);
node->idloc = tok.loc;
gettoken();
if (tok.type == TOK_EOF) {
grecs_error(&tok.loc, 0, "unexpected EOF");
grecs_node_free(node);
return 0;
}
if (tok.type != TOK_EQ) {
grecs_error(&tok.loc, 0,
"expected `=', but found `%s'", tok.buf);
error_recovery();
grecs_node_free(node);
return 1;
}
node->v.value = getvalue();
node->locus.end = node->v.value->locus.end;
grecs_node_bind(parent, node, 1);
return 1;
}
static void
read_statement_list(struct grecs_node *parent)
{
while (read_statement(parent))
;
}
struct grecs_node *
create_subsection_node(struct grecs_node *root)
{
struct grecs_list_entry *ep;
struct grecs_node *p;
for (ep = tok.path->head; ep; ep = ep->next) {
char *ident = ep->data;
p = grecs_find_node(root, ident);
if (!p) {
p = grecs_node_create(grecs_node_block, &tok.loc);
p->ident = grecs_strdup(ident);
grecs_node_bind(root, p, 1);
}
root = p;
}
return root;
}
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 = create_subsection_node(parent);
read_statement_list(node);
} else if (tok.type == TOK_KEYWORD) {
read_statement(parent);
} else {
grecs_error(&tok.loc, 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_point.file = grecs_install_text(name);
grecs_current_locus_point.line = 1;
grecs_current_locus_point.col = 0;
acc = grecs_txtacc_create();
tok.path = grecs_list_create();
root = grecs_node_create(grecs_node_root, &tok.loc);
while (read_section(root))
;
root->locus.end = grecs_current_locus_point;
fclose(infile);
grecs_txtacc_free(acc);
grecs_list_free(tok.path);
if (grecs_error_count) {
grecs_tree_free(root);
root = NULL;
}
return root;
}