You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lighttpd2/src/main/config_parser.rl

1500 lines
44 KiB

#include <lighttpd/base.h>
#include <lighttpd/config_parser.h>
15 years ago
#ifdef HAVE_LUA_H
# include <lighttpd/config_lua.h>
#endif
#if 0
#define _printf(fmt, ...) g_print(fmt, __VA_ARGS__)
#else
#define _printf(fmt, ...) /* */
#endif
15 years ago
/** config parser state machine **/
static gboolean config_parser_include(liServer *srv, GList *ctx_stack, gchar *param);
15 years ago
%%{
## ragel stuff
15 years ago
machine config_parser;
variable p ctx->p;
variable pe ctx->pe;
variable eof ctx->eof;
15 years ago
access ctx->;
15 years ago
prepush {
/* _printf("current stacksize: %d, top: %d\n", ctx->stacksize, ctx->top); */
15 years ago
/* increase stacksize if necessary */
if (ctx->stacksize == ctx->top)
15 years ago
{
/* increase stacksize by 8 */
ctx->stack = g_realloc(ctx->stack, sizeof(int) * (ctx->stacksize + 8));
ctx->stacksize += 8;
15 years ago
}
}
## actions
action mark { ctx->mark = fpc; }
# basic types
action none {
liValue *v;
v = li_value_new_none();
g_queue_push_head(ctx->value_stack, v);
_printf("got 'none' value in line %zd\n", ctx->line);
}
15 years ago
action boolean {
liValue *v;
v = li_value_new_bool(*ctx->mark == 't' ? TRUE : FALSE);
g_queue_push_head(ctx->value_stack, v);
_printf("got boolean %s in line %zd\n", *ctx->mark == 't' ? "true" : "false", ctx->line);
15 years ago
}
action integer {
liValue *v;
gint64 i = 0;
for (gchar *c = ctx->mark; c < fpc; c++)
i = i * 10 + *c - 48;
v = li_value_new_number(i);
/* push value onto stack */
g_queue_push_head(ctx->value_stack, v);
_printf("got integer %" G_GINT64_FORMAT " in line %zd\n", i, ctx->line);
15 years ago
}
15 years ago
action integer_suffix {
liValue *v;
GString *str;
v = g_queue_peek_head(ctx->value_stack);
str = g_string_new_len(ctx->mark, fpc - ctx->mark);
if (g_str_equal(str->str, "kbyte")) v->data.number *= 1024;
else if (g_str_equal(str->str, "mbyte")) v->data.number *= 1024 * 1024;
else if (g_str_equal(str->str, "gbyte")) v->data.number *= 1024 * 1024 * 1024;
else if (g_str_equal(str->str, "tbyte")) v->data.number *= 1024 * 1024 * 1024 * G_GINT64_CONSTANT(1024);
else if (g_str_equal(str->str, "kbit")) v->data.number *= 1024 / 8;
else if (g_str_equal(str->str, "mbit")) v->data.number *= 1024 * 1024 / 8;
else if (g_str_equal(str->str, "gbit")) v->data.number *= 1024 * 1024 * 1024 / 8;
else if (g_str_equal(str->str, "tbit")) v->data.number *= 1024 * 1024 * 1024 * G_GINT64_CONSTANT(1024) / 8;
else if (g_str_equal(str->str, "min")) v->data.number *= 60;
else if (g_str_equal(str->str, "hours")) v->data.number *= 60 * 60;
else if (g_str_equal(str->str, "days")) v->data.number *= 60 * 60 * 24;
g_string_free(str, TRUE);
_printf("got int with suffix: %" G_GINT64_FORMAT "\n", v->data.number);
15 years ago
}
action string {
liValue *v;
GString *str;
gchar ch;
str = g_string_sized_new(fpc - ctx->mark - 2);
for (gchar *c = (ctx->mark+1); c != (fpc-1); c++) {
if (*c != '\\')
g_string_append_c(str, *c);
else {
guint avail = fpc - 1 - c;
if (avail == 0) {
ERROR(srv, "%s", "invalid \\ at end of string");
g_string_free(str, TRUE);
return FALSE;
}
switch (*(c+1)) {
case '\\': g_string_append_c(str, '\\'); c++; break;
case '"': g_string_append_c(str, '"'); c++; break;
case 'n': g_string_append_c(str, '\n'); c++; break;
case 'r': g_string_append_c(str, '\r'); c++; break;
case 't': g_string_append_c(str, '\t'); c++; break;
case 'x':
if (avail < 3 || !(
((*(c+2) >= '0' && *(c+2) <= '9') || (*(c+2) >= 'A' && *(c+2) <= 'F') || (*(c+2) >= 'a' && *(c+2) <= 'f')) &&
((*(c+3) >= '0' && *(c+3) <= '9') || (*(c+3) >= 'A' && *(c+3) <= 'F') || (*(c+3) >= 'a' && *(c+3) <= 'f')))) {
ERROR(srv, "%s", "invalid \\xHH in string");
g_string_free(str, TRUE);
return FALSE;
}
/* append char from hex */
/* first char */
if (*(c+2) <= '9')
ch = 16 * (*(c+2) - '0');
else if (*(c+2) <= 'F')
ch = 16 * (10 + *(c+2) - 'A');
else
ch = 16 * (10 + *(c+2) - 'a');
/* second char */
if (*(c+3) <= '9')
ch += *(c+3) - '0';
else if (*(c+3) <= 'F')
ch += 10 + *(c+3) - 'A';
else
ch += 10 + *(c+3) - 'a';
c += 3;
g_string_append_c(str, ch);
break;
default:
g_string_append_c(str, '\\');
}
}
}
15 years ago
v = li_value_new_string(str);
g_queue_push_head(ctx->value_stack, v);
_printf("got string %s", "");
for (gchar *c = ctx->mark + 1; c < fpc - 1; c++) _printf("%c", *c);
_printf(" in line %zd\n", ctx->line);
15 years ago
}
# advanced types
action list_start {
liValue *v;
/* create new list value and put it on stack, list entries are put in it by getting the previous value from the stack */
v = li_value_new_list();
g_queue_push_head(ctx->value_stack, v);
fcall list_scanner;
}
action list_push {
liValue *v, *l;
/* pop current value from stack and append it to the new top of the stack value (the list) */
v = g_queue_pop_head(ctx->value_stack);
l = g_queue_peek_head(ctx->value_stack);
assert(l->type == LI_VALUE_LIST);
g_array_append_val(l->data.list, v);
_printf("list_push %s\n", li_value_type_string(v->type));
}
action list_end {
fret;
}
action hash_start {
liValue *v;
/* create new hash value and put it on stack, if a key-value pair is encountered, get it by walking 2 steps back the stack */
v = li_value_new_hash();
g_queue_push_head(ctx->value_stack, v);
fcall hash_scanner;
15 years ago
}
action hash_push {
liValue *k, *v, *h; /* key value hashtable */
GString *str;
15 years ago
v = g_queue_pop_head(ctx->value_stack);
k = g_queue_pop_head(ctx->value_stack);
h = g_queue_peek_head(ctx->value_stack);
/* duplicate key so value can be free'd */
str = g_string_new_len(k->data.string->str, k->data.string->len);
g_hash_table_insert(h->data.hash, str, v);
_printf("hash_push: %s: %s => %s\n", li_value_type_string(k->type), li_value_type_string(v->type), li_value_type_string(h->type));
li_value_free(k);
15 years ago
}
action hash_end {
fret;
}
action block_start {
_printf("%s", "block_start\n");
fcall block_scanner;
}
15 years ago
action block_end {
_printf("%s", "block_end\n");
fret;
15 years ago
}
action keyvalue_start {
/* fpc--; */
_printf("keyvalue start in line %zd\n", ctx->line);
fcall key_value_scanner;
15 years ago
}
action keyvalue_end {
liValue *k, *v, *l;
/* we have a key and a value on the stack; convert them to a list with 2 elements */
15 years ago
v = g_queue_pop_head(ctx->value_stack);
k = g_queue_pop_head(ctx->value_stack);
15 years ago
l = li_value_new_list();
15 years ago
g_array_append_val(l->list, k);
g_array_append_val(l->list, v);
15 years ago
_printf("key-value pair: %s => %s in line %zd\n", li_value_type_string(k->type), li_value_type_string(v->type), ctx->line);
/* push list on the stack */
g_queue_push_head(ctx->value_stack, l);
15 years ago
/* fpc--; */
fret;
}
15 years ago
action liValue {
liValue *v;
v = g_queue_peek_head(ctx->value_stack);
/* check if we need to cast the value */
if (ctx->cast != LI_CFG_PARSER_CAST_NONE) {
if (ctx->cast == LI_CFG_PARSER_CAST_INT) {
/* cast string to integer */
gint x = 0;
guint i = 0;
gboolean negative = FALSE;
if (v->type != LI_VALUE_STRING) {
ERROR(srv, "can only cast strings to integers, %s given", li_value_type_string(v->type));
return FALSE;
}
if (v->data.string->str[0] == '-') {
negative = TRUE;
i++;
}
for (; i < v->data.string->len; i++) {
gchar c = v->data.string->str[i];
if (c < '0' || c > '9') {
ERROR(srv, "%s", "cast(int) parameter doesn't look like a numerical string");
return FALSE;
}
x = x * 10 + c - '0';
}
if (negative)
x *= -1;
g_string_free(v->data.string, TRUE);
v->data.number = x;
v->type = LI_VALUE_NUMBER;
}
else if (ctx->cast == LI_CFG_PARSER_CAST_STR) {
/* cast integer to string */
GString *str;
if (v->type != LI_VALUE_NUMBER) {
ERROR(srv, "can only cast integers to strings, %s given", li_value_type_string(v->type));
return FALSE;
}
str = g_string_sized_new(0);
g_string_printf(str, "%" G_GINT64_FORMAT, v->data.number);
v->data.string = str;
v->type = LI_VALUE_STRING;
}
ctx->cast = LI_CFG_PARSER_CAST_NONE;
}
_printf("value (%s) in line %zd\n", li_value_type_string(v->type), ctx->line);
}
action value_statement_start {
fcall value_statement_scanner;
}
action value_statement_end {
fret;
}
15 years ago
action value_statement_op {
g_queue_push_head(ctx->value_op_stack, ctx->mark);
}
action value_statement {
/* value (+|-|*|/) value */
/* compute new value out of the two */
liValue *l, *r, *v;
gboolean free_l, free_r;
gchar op;
free_l = free_r = TRUE;
r = g_queue_pop_head(ctx->value_stack);
l = g_queue_pop_head(ctx->value_stack);
v = NULL;
op = *(gchar*)g_queue_pop_head(ctx->value_op_stack);
if (op == '=') {
/* value => value */
free_l = FALSE;
free_r = FALSE;
v = li_value_new_list();
g_array_append_val(v->data.list, l);
g_array_append_val(v->data.list, r);
} else if (l->type == LI_VALUE_NUMBER && r->type == LI_VALUE_NUMBER) {
switch (op) {
case '+': v = li_value_new_number(l->data.number + r->data.number); break;
case '-': v = li_value_new_number(l->data.number - r->data.number); break;
case '*': v = li_value_new_number(l->data.number * r->data.number); break;
case '/': v = li_value_new_number(l->data.number / r->data.number); break;
}
} else if (l->type == LI_VALUE_STRING) {
v = l;
free_l = FALSE;
if (r->type == LI_VALUE_STRING && op == '+') {
/* str + str */
v->data.string = g_string_append_len(v->data.string, GSTR_LEN(r->data.string));
} else if (r->type == LI_VALUE_NUMBER && op == '+') {
/* str + int */
g_string_append_printf(v->data.string, "%" G_GINT64_FORMAT, r->data.number);
} else if (r->type == LI_VALUE_NUMBER && op == '*') {
/* str * int */
if (r->data.number < 0) {
ERROR(srv, "string multiplication with negative number (%" G_GINT64_FORMAT ")?", r->data.number);
return FALSE;
} else if (r->data.number == 0) {
v->data.string = g_string_truncate(v->data.string, 0);
} else {
GString *str;
str = g_string_new_len(l->data.string->str, l->data.string->len);
for (gint i = 1; i < r->data.number; i++)
v->data.string = g_string_append_len(v->data.string, str->str, str->len);
g_string_free(str, TRUE);
}
}
else
v = NULL;
} else if (l->type == LI_VALUE_LIST) {
if (op == '+') {
/* append r to the end of l */
free_l = FALSE; /* use l as the new o */
free_r = FALSE; /* r gets appended to o */
v = l;
g_array_append_val(l->data.list, r);
} else if (op == '*') {
/* merge l and r */
if (r->type == LI_VALUE_LIST) {
/* merge lists */
free_l = FALSE;
g_array_append_vals(l->data.list, r->data.list->data, r->data.list->len);
g_array_set_size(r->data.list, 0);
v = l;
}
}
}
else if (l->type == LI_VALUE_HASH && r->type == LI_VALUE_HASH && op == '+') {
/* merge hashtables */
GHashTableIter iter;
gpointer key, val;
free_l = FALSE; /* keep l, it's the new o */
v = l;
g_hash_table_iter_init(&iter, r->data.hash);
while (g_hash_table_iter_next(&iter, &key, &val)) {
g_hash_table_insert(v->data.hash, key, val);
g_hash_table_iter_steal(&iter); /* steal key->value so it doesn't get deleted when destroying r */
}
}
if (v == NULL) {
WARNING(srv, "erronous value statement: %s %c %s in line %zd\n",
li_value_type_string(l->type), op,
li_value_type_string(r->type), ctx->line);
return FALSE;
}
_printf("value statement: %s %c%s %s => %s in line %zd\n",
li_value_type_string(l->type),
op,
op == '=' ? ">" : "",
li_value_type_string(r->type),
li_value_type_string(v->type),
ctx->line);
if (free_l)
li_value_free(l);
if (free_r)
li_value_free(r);
g_queue_push_head(ctx->value_stack, v);
}
action varname {
/* varname, push it as string value onto the stack */
liValue *v;
GString *str;
15 years ago
str = g_string_new_len(ctx->mark, fpc - ctx->mark);
v = li_value_new_string(str);
g_queue_push_head(ctx->value_stack, v);
_printf("got varname %s\n", str->str);
}
15 years ago
action actionref {
/* varname is on the stack */
liValue *name, *v;
name = g_queue_pop_head(ctx->value_stack);
assert(name->type == LI_VALUE_STRING);
_printf("got actionref: %s in line %zd\n", name->data.string->str, ctx->line);
/* there are some special variables that we just create here */
if (g_str_equal(name->data.string->str, "sys.pid")) {
v = li_value_new_number(getpid());
} else if (g_str_equal(name->data.string->str, "sys.cwd")) {
gchar cwd[1024];
if (NULL != getcwd(cwd, 1023)) {
v = li_value_new_string(g_string_new(cwd));
} else {
ERROR(srv, "failed to get CWD: %s", g_strerror(errno));
li_value_free(name);
return FALSE;
}
} else if (g_str_equal(name->data.string->str, "sys.version")) {
v = li_value_new_string(g_string_new(PACKAGE_VERSION));
} else if (g_str_has_prefix(name->data.string->str, "sys.env.")) {
/* look up string in environment, push value onto stack */
gchar *env = getenv(name->data.string->str + sizeof("sys.env.") - 1);
if (env == NULL) {
ERROR(srv, "unknown environment variable: %s", name->data.string->str + sizeof("sys.env.") - 1);
li_value_free(name);
return FALSE;
}
v = li_value_new_string(g_string_new(env));
} else {
/* look up uservar in hashtable, copy and push value onto stack */
v = g_hash_table_lookup(ctx->uservars, name->data.string);
if (v == NULL) {
WARNING(srv, "unknown uservar '%s'", name->data.string->str);
li_value_free(name);
return FALSE;
}
v = li_value_copy(v);
}
g_queue_push_head(ctx->value_stack, v);
li_value_free(name);
}
action operator {
if ((fpc - ctx->mark) == 1) {
switch (*ctx->mark) {
case '<': ctx->op = LI_CONFIG_COND_LT; break;
case '>': ctx->op = LI_CONFIG_COND_GT; break;
}
} else {
if (*ctx->mark == '>' && *(ctx->mark+1) == '=') ctx->op = LI_CONFIG_COND_GE;
else if (*ctx->mark == '<' && *(ctx->mark+1) == '=') ctx->op = LI_CONFIG_COND_LE;
else if (*ctx->mark == '=' && *(ctx->mark+1) == '=') ctx->op = LI_CONFIG_COND_EQ;
else if (*ctx->mark == '!' && *(ctx->mark+1) == '=') ctx->op = LI_CONFIG_COND_NE;
else if (*ctx->mark == '=' && *(ctx->mark+1) == '^') ctx->op = LI_CONFIG_COND_PREFIX;
else if (*ctx->mark == '!' && *(ctx->mark+1) == '^') ctx->op = LI_CONFIG_COND_NOPREFIX;
else if (*ctx->mark == '=' && *(ctx->mark+1) == '$') ctx->op = LI_CONFIG_COND_SUFFIX;
else if (*ctx->mark == '!' && *(ctx->mark+1) == '$') ctx->op = LI_CONFIG_COND_NOSUFFIX;
else if (*ctx->mark == '=' && *(ctx->mark+1) == '~') ctx->op = LI_CONFIG_COND_MATCH;
else if (*ctx->mark == '!' && *(ctx->mark+1) == '~') ctx->op = LI_CONFIG_COND_NOMATCH;
else if (*ctx->mark == '=' && *(ctx->mark+1) == '/') ctx->op = LI_CONFIG_COND_IP;
else if (*ctx->mark == '!' && *(ctx->mark+1) == '/') ctx->op = LI_CONFIG_COND_NOTIP;
}
}
# statements
action action_call_noparam {
ctx->action_call_with_param = FALSE;
}
action action_call_param {
ctx->action_call_with_param = TRUE;
}
action action_call {
liValue *val, *name, *uservar;
liAction *a, *al;
if (ctx->action_call_with_param) {
/* top of the stack is the value, then the varname as string value */
val = g_queue_pop_head(ctx->value_stack);
name = g_queue_pop_head(ctx->value_stack);
} else {
val = NULL;
name = g_queue_pop_head(ctx->value_stack);
}
ctx->action_call_with_param = FALSE;
15 years ago
assert(name->type == LI_VALUE_STRING);
15 years ago
_printf("got action call: %s %s; in line %zd\n", name->data.string->str, val ? li_value_type_string(val->type) : "<none>", ctx->line);
/* internal functions */
if (g_str_equal(name->data.string->str, "include")) {
li_value_free(name);
if (ctx->in_setup_block) {
WARNING(srv, "%s", "include directives not supported in setup blocks");
return FALSE;
}
if (!val) {
WARNING(srv, "%s", "include directive takes a string as parameter");
return FALSE;
}
if (!config_parser_include(srv, ctx_stack, val->data.string->str)) {
li_value_free(val);
return FALSE;
}
li_value_free(val);
} else if (g_str_equal(name->data.string->str, "include_shell")) {
li_value_free(name);
if (ctx->in_setup_block) {
WARNING(srv, "%s", "include directives not supported in setup blocks");
return FALSE;
}
if (!val) {
WARNING(srv, "%s", "include_shell directive takes a string as parameter");
return FALSE;
}
if (val->type != LI_VALUE_STRING) {
WARNING(srv, "include_shell directive takes a string as parameter, %s given", li_value_type_string(val->type));
li_value_free(val);
return FALSE;
}
if (!config_parser_shell(srv, ctx_stack, val->data.string->str)) {
li_value_free(val);
return FALSE;
}
li_value_free(val);
}
#ifdef HAVE_LUA_H
else if (g_str_equal(name->data.string->str, "include_lua")) {
li_value_free(name);
if (ctx->in_setup_block) {
WARNING(srv, "%s", "include directives not supported in setup blocks");
return FALSE;
}
if (!val) {
WARNING(srv, "%s", "include_lua directive takes a string as parameter");
return FALSE;
}
if (val->type != LI_VALUE_STRING) {
WARNING(srv, "include_lua directive takes a string as parameter, %s given", li_value_type_string(val->type));
li_value_free(val);
return FALSE;
}
if (!li_config_lua_load(&srv->LL, srv, srv->main_worker, val->data.string->str, &a, TRUE, NULL)) {
ERROR(srv, "include_lua '%s' failed", val->data.string->str);
li_value_free(val);
return FALSE;
}
/* include lua doesn't need to produce an action */
if (a != NULL) {
al = g_queue_peek_head(ctx->action_list_stack);
g_array_append_val(al->data.list, a);
}
li_value_free(val);
}
#endif
else if (g_str_has_prefix(name->data.string->str, "__")) {
if (g_str_equal(name->data.string->str + 2, "print")) {
GString *tmpstr;
if (!val) {
WARNING(srv, "%s", "__print directive needs a parameter");
return FALSE;
}
tmpstr = li_value_to_string(val);
DEBUG(srv, "%s:%zd type: %s, value: %s", ctx->filename, ctx->line, li_value_type_string(val->type), tmpstr->str);
g_string_free(tmpstr, TRUE);
}
li_value_free(name);
if (val)
li_value_free(val);
}
/* normal action call */
else {
/* user defined action */
if (NULL != (uservar = g_hash_table_lookup(ctx->uservars, name->data.string))) {
_printf("%s", "... which is a user defined action\n");
if (uservar->type != LI_VALUE_ACTION) {
WARNING(srv, "value of type action expected, got %s", li_value_type_string(uservar->type));
li_value_free(name);
if (val)
li_value_free(val);
return FALSE;
} else if (val) {
WARNING(srv, "%s", "user defined actions don't take a parameter");
li_value_free(name);
li_value_free(val);
return FALSE;
} else if (ctx->in_setup_block) {
WARNING(srv, "%s", "user defined actions can't be called in a setup block");
li_value_free(name);
return FALSE;
}
a = uservar->data.val_action.action;
li_action_acquire(a);
al = g_queue_peek_head(ctx->action_list_stack);
g_array_append_val(al->data.list, a);
}
/* setup action */
else if (ctx->in_setup_block) {
_printf("%s", "... which is a setup action\n");
if (!li_plugin_config_setup(srv, name->data.string->str, val)) {
li_value_free(name);
return FALSE;
}
}
/* normal action */
else {
_printf("%s", "... which is a normal action\n");
al = g_queue_peek_head(ctx->action_list_stack);
a = li_plugin_config_action(srv, srv->main_worker, name->data.string->str, val);
if (a == NULL) {
li_value_free(name);
return FALSE;
}
g_array_append_val(al->data.list, a);
}
li_value_free(name);
}
}
action setup_block_start {
_printf("setup block start in line %zd\n", ctx->line);
if (ctx->in_setup_block) {
WARNING(srv, "%s", "already in a setup block");
return FALSE;
}
ctx->in_setup_block = TRUE;
}
action setup_block_end {
liValue *v;
_printf("setup block end in line %zd\n", ctx->line);
/* pop option stack */
v = g_queue_pop_head(ctx->value_stack);
li_value_free(v);
ctx->in_setup_block = FALSE;
}
action uservar_definition {
/* assignment vor user defined variable, insert into hashtable */
liValue *name, *v;
GString *str;
gpointer old_key;
gpointer old_val;
v = g_queue_pop_head(ctx->value_stack);
name = g_queue_pop_head(ctx->value_stack);
assert(name->type == LI_VALUE_STRING);
_printf("uservar definition %s = %s in line %zd\n", name->data.string->str, li_value_type_string(v->type), ctx->line);
if (NULL != g_hash_table_lookup(srv->setups, name->data.string->str)) {
WARNING(srv, "cannot define uservar with name '%s', a setup action with same name exists already", name->data.string->str);
li_value_free(name);
li_value_free(v);
return FALSE;
}
if (NULL != g_hash_table_lookup(srv->actions, name->data.string->str)) {
WARNING(srv, "cannot define uservar with name '%s', an action with same name exists already", name->data.string->str);
li_value_free(name);
li_value_free(v);
return FALSE;
}
if (NULL != g_hash_table_lookup(srv->options, name->data.string->str)) {
WARNING(srv, "cannot define uservar with name '%s', an option with same name exists already", name->data.string->str);
li_value_free(name);
li_value_free(v);
return FALSE;
}
if (NULL != g_hash_table_lookup(srv->optionptrs, name->data.string->str)) {
WARNING(srv, "cannot define uservar with name '%s', an option with same name exists already", name->data.string->str);
li_value_free(name);
li_value_free(v);
return FALSE;
}
str = li_value_extract_string(name);
/* free old key and value if we are overwriting it */
if (g_hash_table_lookup_extended(ctx->uservars, str, &old_key, &old_val)) {
g_hash_table_remove(ctx->uservars, str);
g_string_free(old_key, TRUE);
li_value_free(old_val);
}
g_hash_table_insert(ctx->uservars, str, v);
li_value_free(name);
}
action action_block_start {
liAction *al;
_printf("action block start in line %zd\n", ctx->line);
/* create new action list and put it on the stack */
al = li_action_new_list();
g_queue_push_head(ctx->action_list_stack, al);
fcall block_scanner;
}