470 lines
14 KiB
Ragel
470 lines
14 KiB
Ragel
#include <lighttpd/angel_base.h>
|
|
#include <lighttpd/angel_config_parser.h>
|
|
|
|
typedef struct {
|
|
GArray *g_stack;
|
|
int *stack;
|
|
int cs, top;
|
|
|
|
/* readingtoken signals that the parser is reading a token; in case
|
|
* the data cames in chunks, the parse function has to append the
|
|
* data to the token and handle tokenstart in the next chunk
|
|
*/
|
|
GString *token;
|
|
gboolean readingtoken;
|
|
|
|
char hexchar;
|
|
gint64 number, number2;
|
|
gboolean negate; /* sign for parsing numbers */
|
|
|
|
/* item */
|
|
GString *itemname;
|
|
liValue *itemvalue;
|
|
|
|
/* every time a collection gets parsed, the collection is pushed on the stack.
|
|
* in some other cases a LI_VALUE_STRING gets pushed (the key in hashes for example)
|
|
* while a second value gets parsed.
|
|
*/
|
|
GPtrArray *valuestack;
|
|
liValue *curvalue;
|
|
|
|
/* temporary buffer to format a character for logging */
|
|
gchar buf[8];
|
|
} pcontext;
|
|
|
|
typedef struct {
|
|
const gchar *filename;
|
|
int line, column;
|
|
} filecontext;
|
|
|
|
GQuark li_angel_config_parser_error_quark() {
|
|
return g_quark_from_static_string("angel-config-parser-error-quark");
|
|
}
|
|
|
|
static gchar *format_char(pcontext *ctx, gchar c) {
|
|
if (g_ascii_isprint(c)) {
|
|
ctx->buf[0] = c;
|
|
ctx->buf[1] = '\0';
|
|
} else switch (c) {
|
|
case '\n': ctx->buf[0] = '\\'; ctx->buf[1] = 'n'; ctx->buf[2] = '\0'; break;
|
|
case '\r': ctx->buf[0] = '\\'; ctx->buf[1] = 'r'; ctx->buf[2] = '\0'; break;
|
|
case '\t': ctx->buf[0] = '\\'; ctx->buf[1] = 't'; ctx->buf[2] = '\0'; break;
|
|
default: g_snprintf(ctx->buf, 8, "\\x%02X", (unsigned int) (unsigned char) c); break;
|
|
}
|
|
return ctx->buf;
|
|
}
|
|
|
|
#define PARSE_ERROR_FMT(fmt, ...) do { \
|
|
g_set_error(err, \
|
|
LI_ANGEL_CONFIG_PARSER_ERROR, \
|
|
LI_ANGEL_CONFIG_PARSER_ERROR_PARSE, \
|
|
"Parsing failed in '%s:%i,%i': " fmt, \
|
|
fctx->filename, fctx->line, fctx->column, \
|
|
__VA_ARGS__); \
|
|
} while (0)
|
|
|
|
#define PARSE_ERROR(msg) PARSE_ERROR_FMT("%s", msg)
|
|
|
|
#define UPDATE_COLUMN() do { \
|
|
fctx->column += p - linestart; \
|
|
linestart = p; \
|
|
} while (0)
|
|
|
|
%%{
|
|
machine angel_config_parser;
|
|
|
|
access ctx->;
|
|
|
|
prepush { update_stack(ctx); }
|
|
postpop { update_stack(ctx); }
|
|
|
|
action starttoken {
|
|
g_string_truncate(ctx->token, 0);
|
|
tokenstart = fpc;
|
|
ctx->readingtoken = TRUE;
|
|
}
|
|
action endtoken {
|
|
g_string_append_len(ctx->token, tokenstart, fpc - tokenstart);
|
|
tokenstart = NULL;
|
|
ctx->readingtoken = FALSE;
|
|
}
|
|
|
|
action newline {
|
|
fctx->line++;
|
|
fctx->column = 1;
|
|
linestart = fpc+1;
|
|
}
|
|
|
|
action startitem {
|
|
ctx->itemname = ctx->token;
|
|
ctx->token = g_string_sized_new(0);
|
|
ctx->itemvalue = li_value_new_hash();
|
|
}
|
|
|
|
action enditem {
|
|
plugins_handle_item(srv, ctx->itemname, ctx->itemvalue);
|
|
g_string_free(ctx->itemname, TRUE);
|
|
ctx->itemname = NULL;
|
|
li_value_free(ctx->itemvalue);
|
|
ctx->itemvalue = NULL;
|
|
}
|
|
|
|
action error_unknown {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR("internal parse error");
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_unexpected_char {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s'", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_id {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected identifier", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_block {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected block ('{...}' or ';') for item '%s'", format_char(ctx, fc), ctx->itemname->str);
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_xdigit {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected hexdigit", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_forbidden_character {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("forbidden character '%s' in string", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_semicolon {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected ';'", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_colon {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected ':'", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_string {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected a string (\"...\")", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_number {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected a number", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_value {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected a value", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_hash_end {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected ']'", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
action error_expected_list_end {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("unexpected character '%s', expected ')'", format_char(ctx, fc));
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
|
|
|
|
|
|
action string_start {
|
|
g_string_truncate(ctx->token, 0);
|
|
}
|
|
|
|
action string_append {
|
|
g_string_append_len(ctx->token, fpc, 1);
|
|
}
|
|
|
|
|
|
action value_true {
|
|
ctx->curvalue = li_value_new_bool(TRUE);
|
|
}
|
|
|
|
action value_false {
|
|
ctx->curvalue = li_value_new_bool(FALSE);
|
|
}
|
|
|
|
action value_number {
|
|
ctx->curvalue = li_value_new_number(ctx->number);
|
|
}
|
|
|
|
action value_range {
|
|
liValueRange vr = { ctx->number2, ctx->number };
|
|
ctx->curvalue = li_value_new_range(vr);
|
|
if (ctx->number2 > ctx->number) {
|
|
GString *tmp = li_value_to_string(ctx->curvalue);
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("range broken: %s (from > to)", tmp->str);
|
|
g_string_free(tmp, TRUE);
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
}
|
|
|
|
action value_string {
|
|
ctx->curvalue = li_value_new_string(ctx->token);
|
|
ctx->token = g_string_sized_new(0);
|
|
}
|
|
|
|
action value_list_start {
|
|
g_ptr_array_add(ctx->valuestack, li_value_new_list());
|
|
ctx->curvalue = NULL;
|
|
fcall value_list_sub;
|
|
}
|
|
action value_list_push {
|
|
liValue *vlist = g_ptr_array_index(ctx->valuestack, ctx->valuestack->len-1);
|
|
g_ptr_array_add(vlist->data.list, ctx->curvalue);
|
|
ctx->curvalue = NULL;
|
|
}
|
|
action value_list_end {
|
|
guint ndx = ctx->valuestack->len - 1;
|
|
ctx->curvalue = g_ptr_array_index(ctx->valuestack, ndx);
|
|
g_ptr_array_set_size(ctx->valuestack, ndx);
|
|
fret;
|
|
}
|
|
|
|
action value_hash_start {
|
|
g_ptr_array_add(ctx->valuestack, li_value_new_hash());
|
|
ctx->curvalue = NULL;
|
|
fcall value_hash_sub;
|
|
}
|
|
action value_hash_push_name {
|
|
g_ptr_array_add(ctx->valuestack, ctx->curvalue);
|
|
ctx->curvalue = NULL;
|
|
}
|
|
action value_hash_push_value {
|
|
guint ndx = ctx->valuestack->len-1;
|
|
liValue *vname = g_ptr_array_index(ctx->valuestack, ndx);
|
|
liValue *vhash = g_ptr_array_index(ctx->valuestack, ndx-1);
|
|
g_ptr_array_set_size(ctx->valuestack, ndx);
|
|
if (NULL != g_hash_table_lookup(vhash->data.hash, vname->data.string)) {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("duplicate key '%s' in item '%s'", vname->data.string->str, ctx->itemname->str);
|
|
li_value_free(vname);
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
g_hash_table_insert(vhash->data.hash, vname->data.string, ctx->curvalue);
|
|
ctx->curvalue = NULL;
|
|
vname->type = LI_VALUE_NONE;
|
|
li_value_free(vname);
|
|
}
|
|
action value_hash_end {
|
|
guint ndx = ctx->valuestack->len - 1;
|
|
ctx->curvalue = g_ptr_array_index(ctx->valuestack, ndx);
|
|
g_ptr_array_set_size(ctx->valuestack, ndx);
|
|
fret;
|
|
}
|
|
|
|
action option_start {
|
|
g_ptr_array_add(ctx->valuestack, li_value_new_string(ctx->token));
|
|
ctx->token = g_string_sized_new(0);
|
|
ctx->curvalue= NULL;
|
|
}
|
|
action option_push {
|
|
guint ndx = ctx->valuestack->len-1;
|
|
liValue *vname = g_ptr_array_index(ctx->valuestack, ndx);
|
|
g_ptr_array_set_size(ctx->valuestack, ndx);
|
|
if (!ctx->curvalue) ctx->curvalue = li_value_new_none();
|
|
if (NULL != g_hash_table_lookup(ctx->itemvalue->data.hash, vname->data.string)) {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR_FMT("duplicate key '%s' in item '%s'", vname->data.string->str, ctx->itemname->str);
|
|
li_value_free(vname);
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
g_hash_table_insert(ctx->itemvalue->data.hash, vname->data.string, ctx->curvalue);
|
|
ctx->curvalue = NULL;
|
|
vname->type = LI_VALUE_NONE;
|
|
li_value_free(vname);
|
|
}
|
|
|
|
line_sane = ( '\n' ) @newline;
|
|
line_weird = ( '\r' ) @newline;
|
|
line_insane = ( '\r\n' ) @{ fctx->line--; };
|
|
line = ( line_sane | line_weird | line_insane );
|
|
# line = ( '\n' | '\r' '\n'? <: '' ) %newline;
|
|
|
|
ws = [\t\v\f ];
|
|
comment = '#' (any - line)* line;
|
|
noise = ( ws | line | comment )+;
|
|
# FIXME for viewing the statemachine
|
|
# noise = ( ws | [\r\n] )+;
|
|
|
|
id = (alpha (alnum | '.' | '-' | '_')** ) >starttoken %endtoken;
|
|
|
|
special_chars = '\\' (
|
|
'n' @{ g_string_append(ctx->token, "\n"); }
|
|
| 't' @{ g_string_append(ctx->token, "\t"); }
|
|
| 'r' @{ g_string_append(ctx->token, "\r"); }
|
|
| 'x'
|
|
(xdigit @{ ctx->hexchar = 16*g_ascii_xdigit_value(fc); }
|
|
xdigit @{ ctx->hexchar += g_ascii_xdigit_value(fc);
|
|
g_string_append_len(ctx->token, &ctx->hexchar, 1);
|
|
} ) @err(error_expected_xdigit)
|
|
| (any - [ntrx] - cntrl) @string_append
|
|
) ;
|
|
string = ('"' @string_start ( (any - ["\\] - cntrl)@string_append | special_chars )* '"' ) <>err(error_forbidden_character);
|
|
# FIXME for viewing the statemachine
|
|
# string = '"' @string_start '"';
|
|
|
|
action number_digit {
|
|
if (ctx->negate) {
|
|
if ((G_MININT64 + (fc - '0')) / 10 > ctx->number) {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR("Integer underflow");
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
ctx->number = ctx->number*10 - (fc - '0');
|
|
} else {
|
|
if ((G_MAXINT64 - (fc - '0')) / 10 < ctx->number) {
|
|
UPDATE_COLUMN();
|
|
PARSE_ERROR("Integer overflow");
|
|
ctx->cs = angel_config_parser_error; fbreak;
|
|
}
|
|
ctx->number = ctx->number*10 + (fc - '0');
|
|
}
|
|
}
|
|
number = (('-'@{ctx->negate = TRUE;})? (digit digit**) $number_digit) >{ ctx->number = 0; ctx->negate = FALSE; };
|
|
|
|
value_bool = ('true'i | 'enabled'i) %value_true | ('false'i | 'disabled'i) %value_false;
|
|
value_number = number noise** ('-'@{ ctx->number2 = ctx->number; } (noise*) $err(error_expected_number) number %value_range | '' %value_number);
|
|
value_string = string @value_string;
|
|
value_list = '(' @value_list_start;
|
|
value_hash = '[' @value_hash_start;
|
|
value = (value_bool | value_number | value_string | value_list | value_hash) @err(error_expected_value);
|
|
# FIXME for viewing the statemachine
|
|
# value = "v";
|
|
|
|
value_list_sub_item = value %value_list_push;
|
|
value_list_sub := ((noise | value_list_sub_item noise* ',')* value_list_sub_item? (noise*) $err(error_expected_list_end) ')' @value_list_end) @err(error_unexpected_char);
|
|
|
|
value_hash_sub_item = (value_string %value_hash_push_name) $err(error_expected_string) (noise*) $err(error_expected_colon) ':' noise* value %value_hash_push_value;
|
|
value_hash_sub := ((noise | value_hash_sub_item noise* ',')* value_hash_sub_item? (noise*) $err(error_expected_hash_end) ']' @value_hash_end) @err(error_unexpected_char);
|
|
|
|
option = id %option_start <: noise* value? (noise*) $err(error_expected_semicolon) ';' @option_push ;
|
|
optionlist = ((noise | option) >err(error_expected_id) )*;
|
|
item = (id %startitem) noise* ( ('{' optionlist '}') | ';' ) >err(error_expected_block) @enditem ;
|
|
|
|
main := ((noise | item) >err(error_expected_id) <>err(error_unexpected_char) )*;
|
|
}%%
|
|
|
|
%% write data;
|
|
|
|
static void update_stack(pcontext *ctx) {
|
|
g_array_set_size(ctx->g_stack, ctx->top + 1);
|
|
ctx->stack = (int*) ctx->g_stack->data;
|
|
}
|
|
|
|
static int angel_config_parser_has_error(pcontext *ctx) {
|
|
return ctx->cs == angel_config_parser_error;
|
|
}
|
|
|
|
static int angel_config_parser_is_finished(pcontext *ctx) {
|
|
return ctx->cs >= angel_config_parser_first_final;
|
|
}
|
|
|
|
static pcontext* angel_config_parser_new() {
|
|
pcontext *ctx = g_slice_new0(pcontext);
|
|
ctx->g_stack = g_array_sized_new(FALSE, FALSE, sizeof(int), 8);
|
|
ctx->top = 0;
|
|
g_array_set_size(ctx->g_stack, ctx->top + 1);
|
|
ctx->stack = (int*) ctx->g_stack->data;
|
|
ctx->token = g_string_sized_new(0);
|
|
ctx->valuestack = g_ptr_array_new();
|
|
|
|
%% write init;
|
|
|
|
return ctx;
|
|
}
|
|
|
|
static void angel_config_parser_free(pcontext *ctx) {
|
|
if (!ctx) return;
|
|
|
|
g_array_free(ctx->g_stack, TRUE);
|
|
g_string_free(ctx->token, TRUE);
|
|
for (guint i = 0; i < ctx->valuestack->len; i++) {
|
|
li_value_free(g_ptr_array_index(ctx->valuestack, i));
|
|
}
|
|
g_ptr_array_free(ctx->valuestack, TRUE);
|
|
if (ctx->itemname) g_string_free(ctx->itemname, TRUE);
|
|
li_value_free(ctx->itemvalue);
|
|
li_value_free(ctx->curvalue);
|
|
g_slice_free(pcontext, ctx);
|
|
}
|
|
|
|
static gboolean angel_config_parser_finalize(pcontext *ctx, filecontext *fctx, GError **err) {
|
|
if (!angel_config_parser_is_finished(ctx)) {
|
|
PARSE_ERROR("unexpected end of file");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean angel_config_parse_data(liServer *srv, pcontext *ctx, filecontext *fctx, gchar *data, gsize len, GError **err) {
|
|
gchar *p = data, *pe = p+len, *eof = NULL, *linestart = p, *tokenstart = NULL;
|
|
if (ctx->readingtoken) tokenstart = p;
|
|
|
|
%% write exec;
|
|
|
|
if (ctx->readingtoken) g_string_append_len(ctx->token, tokenstart, p - tokenstart);
|
|
|
|
UPDATE_COLUMN();
|
|
fctx->column--;
|
|
|
|
if (err && *err) return FALSE;
|
|
|
|
if (angel_config_parser_has_error(ctx)) {
|
|
PARSE_ERROR("unknown parser error");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean li_angel_config_parse_file(liServer *srv, const gchar *filename, GError **err) {
|
|
char *data = NULL;
|
|
gsize len = 0;
|
|
filecontext sfctx, *fctx = &sfctx;
|
|
pcontext *ctx = angel_config_parser_new();
|
|
|
|
if (err && *err) goto error;
|
|
|
|
if (!g_file_get_contents(filename, &data, &len, err)) goto error;
|
|
|
|
sfctx.filename = filename;
|
|
sfctx.line = 1;
|
|
sfctx.column = 1;
|
|
if (!angel_config_parse_data(srv, ctx, fctx, data, len, err)) goto error;
|
|
if (!angel_config_parser_finalize(ctx, fctx, err)) goto error;
|
|
|
|
if (data) g_free(data);
|
|
angel_config_parser_free(ctx);
|
|
return TRUE;
|
|
|
|
error:
|
|
if (data) g_free(data);
|
|
angel_config_parser_free(ctx);
|
|
return FALSE;
|
|
}
|