/* 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; }