Refactor: Extract parser stack implementation to parser_util (#6514)

The command and config parsers both used a similar stack implementation
for handling tokens. This resulted in duplicated code.

This commit extracts the common stack implementation (push/get/clear
functions for strings and longs) into a new `parser_util` module.

This resolves a long standing TODO comment.
This commit is contained in:
Orestis Floros
2025-11-05 22:56:49 +01:00
committed by GitHub
parent 197bad6f59
commit e035d0c658
9 changed files with 212 additions and 241 deletions

View File

@ -147,11 +147,12 @@ for my $state (@keys) {
$next_state ||= 'INITIAL'; $next_state ||= 'INITIAL';
my $fmt = $cmd; my $fmt = $cmd;
# Replace the references to identified literals (like $workspace) with # Replace the references to identified literals (like $workspace) with
# calls to get_string(). Also replaces state names (like FOR_WINDOW) # calls to parser_get_string(). Also replaces state names (like
# with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.). # FOR_WINDOW) with their ID (useful for cfg_criteria_init(FOR_WINDOW)
# e.g.).
$cmd =~ s/$_/$statenum{$_}/g for @keys; $cmd =~ s/$_/$statenum{$_}/g for @keys;
$cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g; $cmd =~ s/\$([a-z_]+)/parser_get_string(stack, "$1")/g;
$cmd =~ s/\&([a-z_]+)/get_long(stack, "$1")/g; $cmd =~ s/\&([a-z_]+)/parser_get_long(stack, "$1")/g;
# For debugging/testing, we print the call using printf() and thus need # For debugging/testing, we print the call using printf() and thus need
# to generate a format string. The format uses %d for <number>s, # to generate a format string. The format uses %d for <number>s,
# literal numbers or state IDs and %s for NULL, <string>s and literal # literal numbers or state IDs and %s for NULL, <string>s and literal

View File

@ -43,7 +43,7 @@ CFGFUN(include, const char *pattern);
CFGFUN(font, const char *font); CFGFUN(font, const char *font);
CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command); CFGFUN(exec, const char *exectype, const char *no_startup_id, const char *command);
CFGFUN(for_window, const char *command); CFGFUN(for_window, const char *command);
CFGFUN(gaps, const char *workspace, const char *type, const long value); CFGFUN(gaps, const char *workspace, const char *scope, const long value);
CFGFUN(smart_borders, const char *enable); CFGFUN(smart_borders, const char *enable);
CFGFUN(smart_gaps, const char *enable); CFGFUN(smart_gaps, const char *enable);
CFGFUN(floating_minimum_size, const long width, const long height); CFGFUN(floating_minimum_size, const long width, const long height);
@ -79,7 +79,7 @@ CFGFUN(default_border, const char *windowtype, const char *border, const long wi
CFGFUN(workspace, const char *workspace, const char *output); CFGFUN(workspace, const char *workspace, const char *output);
CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command); CFGFUN(binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
CFGFUN(enter_mode, const char *pango_markup, const char *mode); CFGFUN(enter_mode, const char *pango_markup, const char *modename);
CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command); CFGFUN(mode_binding, const char *bindtype, const char *modifiers, const char *key, const char *release, const char *border, const char *whole_window, const char *exclude_titlebar, const char *command);
CFGFUN(bar_font, const char *font); CFGFUN(bar_font, const char *font);
@ -103,7 +103,7 @@ CFGFUN(bar_i3bar_command, const char *i3bar_command);
CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text); CFGFUN(bar_color, const char *colorclass, const char *border, const char *background, const char *text);
CFGFUN(bar_socket_path, const char *socket_path); CFGFUN(bar_socket_path, const char *socket_path);
CFGFUN(bar_tray_output, const char *output); CFGFUN(bar_tray_output, const char *output);
CFGFUN(bar_tray_padding, const long spacing_px); CFGFUN(bar_tray_padding, const long padding_px);
CFGFUN(bar_color_single, const char *colorclass, const char *color); CFGFUN(bar_color_single, const char *colorclass, const char *color);
CFGFUN(bar_status_command, const char *command); CFGFUN(bar_status_command, const char *command);
CFGFUN(bar_workspace_command, const char *command); CFGFUN(bar_workspace_command, const char *command);

View File

@ -13,26 +13,11 @@
#include <yajl/yajl_gen.h> #include <yajl/yajl_gen.h>
#include "parser_util.h"
SLIST_HEAD(variables_head, Variable); SLIST_HEAD(variables_head, Variable);
extern pid_t config_error_nagbar_pid; extern pid_t config_error_nagbar_pid;
struct stack_entry {
/* Just a pointer, not dynamically allocated. */
const char *identifier;
enum {
STACK_STR = 0,
STACK_LONG = 1,
} type;
union {
char *str;
long num;
} val;
};
struct stack {
struct stack_entry stack[10];
};
struct parser_ctx { struct parser_ctx {
bool use_nagbar; bool use_nagbar;

59
include/parser_util.h Normal file
View File

@ -0,0 +1,59 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* parser_util.h: utility functions for the config and commands parser
*
*/
#pragma once
struct stack_entry {
/* Just a pointer, not dynamically allocated. */
const char *identifier;
enum {
STACK_STR = 0,
STACK_LONG = 1,
} type;
union {
char *str;
long num;
} val;
};
struct stack {
struct stack_entry stack[10];
};
/**
* Pushes a string (identified by 'identifier') on the stack.
* If a string with the same identifier is already on the stack, the new
* string will be appended, separated by a comma.
*
*/
void parser_push_string(struct stack *stack, const char *identifier, const char *str);
/**
* Pushes a long (identified by 'identifier') on the stack.
*
*/
void parser_push_long(struct stack *stack, const char *identifier, long num);
/**
* Returns the string with the given identifier.
*
*/
const char *parser_get_string(const struct stack *stack, const char *identifier);
/**
* Returns the long with the given identifier.
*
*/
long parser_get_long(const struct stack *stack, const char *identifier);
/**
* Clears the stack.
*
*/
void parser_clear_stack(struct stack *stack);

View File

@ -403,6 +403,7 @@ i3srcs = [
'src/match.c', 'src/match.c',
'src/move.c', 'src/move.c',
'src/output.c', 'src/output.c',
'src/parser_util.c',
'src/randr.c', 'src/randr.c',
'src/regex.c', 'src/regex.c',
'src/render.c', 'src/render.c',
@ -685,6 +686,7 @@ executable(
'test.commands_parser', 'test.commands_parser',
[ [
'src/commands_parser.c', 'src/commands_parser.c',
'src/parser_util.c',
command_parser, command_parser,
], ],
include_directories: inc, include_directories: inc,
@ -697,6 +699,7 @@ executable(
'test.config_parser', 'test.config_parser',
[ [
'src/config_parser.c', 'src/config_parser.c',
'src/parser_util.c',
config_parser, config_parser,
], ],
include_directories: inc, include_directories: inc,

View File

@ -24,6 +24,7 @@
* *
*/ */
#include "all.h" #include "all.h"
#include "parser_util.h"
// Macros to make the YAJL API a bit easier to use. // Macros to make the YAJL API a bit easier to use.
#define y(x, ...) (command_output.json_gen != NULL ? yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__) : 0) #define y(x, ...) (command_output.json_gen != NULL ? yajl_gen_##x(command_output.json_gen, ##__VA_ARGS__) : 0)
@ -56,93 +57,6 @@ typedef struct tokenptr {
#include "GENERATED_command_tokens.h" #include "GENERATED_command_tokens.h"
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
static void push_string(struct stack *stack, const char *identifier, char *str) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].identifier != NULL) {
continue;
}
/* Found a free slot, lets store it here. */
stack->stack[c].identifier = identifier;
stack->stack[c].val.str = str;
stack->stack[c].type = STACK_STR;
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means theres either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(EXIT_FAILURE);
}
// TODO move to a common util
static void push_long(struct stack *stack, const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].identifier != NULL) {
continue;
}
stack->stack[c].identifier = identifier;
stack->stack[c].val.num = num;
stack->stack[c].type = STACK_LONG;
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means theres either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: commands_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(EXIT_FAILURE);
}
// TODO move to a common util
static const char *get_string(struct stack *stack, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].identifier == NULL) {
break;
}
if (strcmp(identifier, stack->stack[c].identifier) == 0) {
return stack->stack[c].val.str;
}
}
return NULL;
}
// TODO move to a common util
static long get_long(struct stack *stack, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].identifier == NULL) {
break;
}
if (strcmp(identifier, stack->stack[c].identifier) == 0) {
return stack->stack[c].val.num;
}
}
return 0;
}
// TODO move to a common util
static void clear_stack(struct stack *stack) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].type == STACK_STR) {
free(stack->stack[c].val.str);
}
stack->stack[c].identifier = NULL;
stack->stack[c].val.str = NULL;
stack->stack[c].val.num = 0;
}
}
/******************************************************************************* /*******************************************************************************
* The parser itself. * The parser itself.
******************************************************************************/ ******************************************************************************/
@ -171,13 +85,13 @@ static void next_state(const cmdp_token *token) {
if (subcommand_output.needs_tree_render) { if (subcommand_output.needs_tree_render) {
command_output.needs_tree_render = true; command_output.needs_tree_render = true;
} }
clear_stack(&stack); parser_clear_stack(&stack);
return; return;
} }
state = token->next_state; state = token->next_state;
if (state == INITIAL) { if (state == INITIAL) {
clear_stack(&stack); parser_clear_stack(&stack);
} }
} }
@ -187,7 +101,7 @@ static void next_state(const cmdp_token *token) {
* workspace commands. * workspace commands.
* *
*/ */
char *parse_string(const char **walk, bool as_word) { char *parse_string(const char **walk, const bool as_word) {
const char *beginning = *walk; const char *beginning = *walk;
/* Handle quoted strings (or words). */ /* Handle quoted strings (or words). */
if (**walk == '"') { if (**walk == '"') {
@ -284,7 +198,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
walk++; walk++;
} }
cmdp_token_ptr *ptr = &(tokens[state]); const cmdp_token_ptr *ptr = &(tokens[state]);
token_handled = false; token_handled = false;
for (c = 0; c < ptr->n; c++) { for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]); token = &(ptr->array[c]);
@ -293,7 +207,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
if (token->name[0] == '\'') { if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
if (token->identifier != NULL) { if (token->identifier != NULL) {
push_string(&stack, token->identifier, sstrdup(token->name + 1)); parser_push_string(&stack, token->identifier, token->name + 1);
} }
walk += strlen(token->name) - 1; walk += strlen(token->name) - 1;
next_state(token); next_state(token);
@ -307,7 +221,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
/* Handle numbers. We only accept decimal numbers for now. */ /* Handle numbers. We only accept decimal numbers for now. */
char *end = NULL; char *end = NULL;
errno = 0; errno = 0;
long int num = strtol(walk, &end, 10); const long int num = strtol(walk, &end, 10);
if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) || if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
(errno != 0 && num == 0)) { (errno != 0 && num == 0)) {
continue; continue;
@ -319,7 +233,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
} }
if (token->identifier != NULL) { if (token->identifier != NULL) {
push_long(&stack, token->identifier, num); parser_push_long(&stack, token->identifier, num);
} }
/* Set walk to the first non-number character */ /* Set walk to the first non-number character */
@ -334,8 +248,9 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
char *str = parse_string(&walk, (token->name[0] != 's')); char *str = parse_string(&walk, (token->name[0] != 's'));
if (str != NULL) { if (str != NULL) {
if (token->identifier) { if (token->identifier) {
push_string(&stack, token->identifier, str); parser_push_string(&stack, token->identifier, str);
} }
free(str);
/* If we are at the end of a quoted string, skip the ending /* If we are at the end of a quoted string, skip the ending
* double quote. */ * double quote. */
if (*walk == '"') { if (*walk == '"') {
@ -441,7 +356,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
y(map_close); y(map_close);
free(position); free(position);
clear_stack(&stack); parser_clear_stack(&stack);
break; break;
} }
} }

View File

@ -52,8 +52,7 @@ CFGFUN(include, const char *pattern) {
file->path = sstrdup(resolved_path); file->path = sstrdup(resolved_path);
TAILQ_INSERT_TAIL(&included_files, file, files); TAILQ_INSERT_TAIL(&included_files, file, files);
struct stack stack; struct stack stack = {0};
memset(&stack, '\0', sizeof(struct stack));
struct parser_ctx ctx = { struct parser_ctx ctx = {
.use_nagbar = result->ctx->use_nagbar, .use_nagbar = result->ctx->use_nagbar,
.stack = &stack, .stack = &stack,
@ -79,7 +78,6 @@ CFGFUN(include, const char *pattern) {
default: default:
/* missing case statement */ /* missing case statement */
assert(false); assert(false);
break;
} }
result->ctx->variables = ctx.variables; /* In case head was modified */ result->ctx->variables = ctx.variables; /* In case head was modified */
} }
@ -97,7 +95,7 @@ static int criteria_next_state;
* commands.c for matching target windows of a command. * commands.c for matching target windows of a command.
* *
*/ */
CFGFUN(criteria_init, int _state) { CFGFUN(criteria_init, const int _state) {
criteria_next_state = _state; criteria_next_state = _state;
DLOG("Initializing criteria, current_match = %p, state = %d\n", current_match, _state); DLOG("Initializing criteria, current_match = %p, state = %d\n", current_match, _state);
@ -244,7 +242,7 @@ CFGFUN(for_window, const char *command) {
TAILQ_INSERT_TAIL(&assignments, assignment, assignments); TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
} }
static void apply_gaps(gaps_t *gaps, gaps_mask_t mask, int value) { static void apply_gaps(gaps_t *gaps, const gaps_mask_t mask, const int value) {
if (gaps == NULL) { if (gaps == NULL) {
return; return;
} }
@ -316,8 +314,8 @@ static gaps_mask_t gaps_scope_to_mask(const char *scope) {
} }
CFGFUN(gaps, const char *workspace, const char *scope, const long value) { CFGFUN(gaps, const char *workspace, const char *scope, const long value) {
int pixels = logical_px(value); const int pixels = logical_px(value);
gaps_mask_t mask = gaps_scope_to_mask(scope); const gaps_mask_t mask = gaps_scope_to_mask(scope);
if (workspace == NULL) { if (workspace == NULL) {
apply_gaps(&config.gaps, mask, pixels); apply_gaps(&config.gaps, mask, pixels);
@ -536,9 +534,9 @@ CFGFUN(workspace, const char *workspace, const char *output) {
struct Workspace_Assignment *assignment; struct Workspace_Assignment *assignment;
/* When a new workspace line is encountered, for the first output word, /* When a new workspace line is encountered, for the first output word,
* $workspace from the config.spec is non-NULL. Afterwards, the parser calls * $workspace from the config.spec is non-NULL. Afterward, the parser calls
* clear_stack() because of the call line. Thus, we have to preserve the * parser_clear_stack() because of the call line. Thus, we have to preserve
* workspace string. */ * the workspace string. */
if (workspace) { if (workspace) {
FREE(current_workspace); FREE(current_workspace);
@ -654,7 +652,7 @@ CFGFUN(assign_output, const char *output) {
TAILQ_INSERT_TAIL(&assignments, assignment, assignments); TAILQ_INSERT_TAIL(&assignments, assignment, assignments);
} }
CFGFUN(assign, const char *workspace, bool is_number) { CFGFUN(assign, const char *workspace, const bool is_number) {
if (current_match->error != NULL) { if (current_match->error != NULL) {
ELOG("match has error: %s\n", current_match->error); ELOG("match has error: %s\n", current_match->error);
return; return;
@ -746,7 +744,7 @@ CFGFUN(bar_id, const char *bar_id) {
} }
CFGFUN(bar_output, const char *output) { CFGFUN(bar_output, const char *output) {
int new_outputs = current_bar->num_outputs + 1; const int new_outputs = current_bar->num_outputs + 1;
current_bar->outputs = srealloc(current_bar->outputs, sizeof(char *) * new_outputs); current_bar->outputs = srealloc(current_bar->outputs, sizeof(char *) * new_outputs);
current_bar->outputs[current_bar->num_outputs] = sstrdup(output); current_bar->outputs[current_bar->num_outputs] = sstrdup(output);
current_bar->num_outputs = new_outputs; current_bar->num_outputs = new_outputs;
@ -810,7 +808,7 @@ static void bar_configure_binding(const char *button, const char *release, const
return; return;
} }
int input_code = atoi(button + strlen("button")); const int input_code = atoi(button + strlen("button"));
if (input_code < 1) { if (input_code < 1) {
ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button); ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
return; return;

View File

@ -72,96 +72,6 @@ typedef struct tokenptr {
#include "GENERATED_config_tokens.h" #include "GENERATED_config_tokens.h"
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
static void push_string(struct stack *ctx, const char *identifier, const char *str) {
for (int c = 0; c < 10; c++) {
if (ctx->stack[c].identifier != NULL &&
strcmp(ctx->stack[c].identifier, identifier) != 0) {
continue;
}
if (ctx->stack[c].identifier == NULL) {
/* Found a free slot, lets store it here. */
ctx->stack[c].identifier = identifier;
ctx->stack[c].val.str = sstrdup(str);
ctx->stack[c].type = STACK_STR;
} else {
/* Append the value. */
char *prev = ctx->stack[c].val.str;
sasprintf(&(ctx->stack[c].val.str), "%s,%s", prev, str);
free(prev);
}
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means theres either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(EXIT_FAILURE);
}
static void push_long(struct stack *ctx, const char *identifier, long num) {
for (int c = 0; c < 10; c++) {
if (ctx->stack[c].identifier != NULL) {
continue;
}
/* Found a free slot, lets store it here. */
ctx->stack[c].identifier = identifier;
ctx->stack[c].val.num = num;
ctx->stack[c].type = STACK_LONG;
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means theres either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: config_parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(EXIT_FAILURE);
}
static const char *get_string(const struct stack *ctx, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (ctx->stack[c].identifier == NULL) {
break;
}
if (strcmp(identifier, ctx->stack[c].identifier) == 0) {
return ctx->stack[c].val.str;
}
}
return NULL;
}
static long get_long(const struct stack *ctx, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (ctx->stack[c].identifier == NULL) {
break;
}
if (strcmp(identifier, ctx->stack[c].identifier) == 0) {
return ctx->stack[c].val.num;
}
}
return 0;
}
static void clear_stack(struct stack *ctx) {
for (int c = 0; c < 10; c++) {
if (ctx->stack[c].type == STACK_STR) {
free(ctx->stack[c].val.str);
}
ctx->stack[c].identifier = NULL;
ctx->stack[c].val.str = NULL;
ctx->stack[c].val.num = 0;
}
}
/******************************************************************************* /*******************************************************************************
* The parser itself. * The parser itself.
******************************************************************************/ ******************************************************************************/
@ -180,12 +90,12 @@ static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
ctx->has_errors = true; ctx->has_errors = true;
} }
_next_state = subcommand_output.next_state; _next_state = subcommand_output.next_state;
clear_stack(ctx->stack); parser_clear_stack(ctx->stack);
} }
ctx->state = _next_state; ctx->state = _next_state;
if (ctx->state == INITIAL) { if (ctx->state == INITIAL) {
clear_stack(ctx->stack); parser_clear_stack(ctx->stack);
} }
/* See if we are jumping back to a state in which we were in previously /* See if we are jumping back to a state in which we were in previously
@ -236,7 +146,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
const char *dumpwalk = input; const char *dumpwalk = input;
int linecnt = 1; int linecnt = 1;
while (*dumpwalk != '\0') { while (*dumpwalk != '\0') {
char *next_nl = strchr(dumpwalk, '\n'); const char *next_nl = strchr(dumpwalk, '\n');
if (next_nl != NULL) { if (next_nl != NULL) {
DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk); DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk);
dumpwalk = next_nl + 1; dumpwalk = next_nl + 1;
@ -275,7 +185,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
walk++; walk++;
} }
cmdp_token_ptr *ptr = &(tokens[ctx->state]); const cmdp_token_ptr *ptr = &(tokens[ctx->state]);
token_handled = false; token_handled = false;
for (c = 0; c < ptr->n; c++) { for (c = 0; c < ptr->n; c++) {
token = &(ptr->array[c]); token = &(ptr->array[c]);
@ -284,7 +194,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
if (token->name[0] == '\'') { if (token->name[0] == '\'') {
if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) { if (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
if (token->identifier != NULL) { if (token->identifier != NULL) {
push_string(ctx->stack, token->identifier, token->name + 1); parser_push_string(ctx->stack, token->identifier, token->name + 1);
} }
walk += strlen(token->name) - 1; walk += strlen(token->name) - 1;
next_state(token, ctx); next_state(token, ctx);
@ -298,7 +208,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
/* Handle numbers. We only accept decimal numbers for now. */ /* Handle numbers. We only accept decimal numbers for now. */
char *end = NULL; char *end = NULL;
errno = 0; errno = 0;
long int num = strtol(walk, &end, 10); const long int num = strtol(walk, &end, 10);
if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) || if ((errno == ERANGE && (num == LONG_MIN || num == LONG_MAX)) ||
(errno != 0 && num == 0)) { (errno != 0 && num == 0)) {
continue; continue;
@ -310,7 +220,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
} }
if (token->identifier != NULL) { if (token->identifier != NULL) {
push_long(ctx->stack, token->identifier, num); parser_push_long(ctx->stack, token->identifier, num);
} }
/* Set walk to the first non-number character */ /* Set walk to the first non-number character */
@ -363,7 +273,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
str[outpos] = beginning[inpos]; str[outpos] = beginning[inpos];
} }
if (token->identifier) { if (token->identifier) {
push_string(ctx->stack, token->identifier, str); parser_push_string(ctx->stack, token->identifier, str);
} }
free(str); free(str);
/* If we are at the end of a quoted string, skip the ending /* If we are at the end of a quoted string, skip the ending
@ -488,7 +398,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
free(error_copy); free(error_copy);
/* Print context lines *after* the error, if any. */ /* Print context lines *after* the error, if any. */
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
char *error_line_end = strchr(error_line, '\n'); const char *error_line_end = strchr(error_line, '\n');
if (error_line_end != NULL && *(error_line_end + 1) != '\0') { if (error_line_end != NULL && *(error_line_end + 1) != '\0') {
error_line = error_line_end + 1; error_line = error_line_end + 1;
error_copy = single_line(error_line); error_copy = single_line(error_line);
@ -506,14 +416,14 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
free(position); free(position);
free(errormessage); free(errormessage);
clear_stack(ctx->stack); parser_clear_stack(ctx->stack);
/* To figure out in which state to go (e.g. MODE or INITIAL), /* To figure out in which state to go (e.g. MODE or INITIAL),
* we find the nearest state which contains an <error> token * we find the nearest state which contains an <error> token
* and follow that one. */ * and follow that one. */
bool error_token_found = false; bool error_token_found = false;
for (int i = ctx->statelist_idx - 1; (i >= 0) && !error_token_found; i--) { for (int i = ctx->statelist_idx - 1; (i >= 0) && !error_token_found; i--) {
cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]); const cmdp_token_ptr *errptr = &(tokens[ctx->statelist[i]]);
for (int j = 0; j < errptr->n; j++) { for (int j = 0; j < errptr->n; j++) {
if (strcmp(errptr->array[j].name, "error") != 0) { if (strcmp(errptr->array[j].name, "error") != 0) {
continue; continue;
@ -594,7 +504,7 @@ int main(int argc, char *argv[]) {
/** /**
* Launch nagbar to indicate errors in the configuration file. * Launch nagbar to indicate errors in the configuration file.
*/ */
void start_config_error_nagbar(const char *configpath, bool has_errors) { void start_config_error_nagbar(const char *configpath, const bool has_errors) {
char *editaction, *pageraction; char *editaction, *pageraction;
sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", configpath); sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", configpath);
sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename); sasprintf(&pageraction, "i3-sensible-pager \"%s\"\n", errorfilename);
@ -687,9 +597,8 @@ static char *get_resource(const char *name) {
* *
*/ */
void free_variables(struct parser_ctx *ctx) { void free_variables(struct parser_ctx *ctx) {
struct Variable *current;
while (!SLIST_EMPTY(&(ctx->variables))) { while (!SLIST_EMPTY(&(ctx->variables))) {
current = SLIST_FIRST(&(ctx->variables)); struct Variable *current = SLIST_FIRST(&(ctx->variables));
FREE(current->key); FREE(current->key);
FREE(current->value); FREE(current->value);
SLIST_REMOVE_HEAD(&(ctx->variables), variables); SLIST_REMOVE_HEAD(&(ctx->variables), variables);
@ -852,14 +761,14 @@ static parse_file_result_t parse_file_inner(struct parser_ctx *ctx, const char *
* 'extra' is negative) */ * 'extra' is negative) */
char *bufcopy = sstrdup(buf); char *bufcopy = sstrdup(buf);
SLIST_FOREACH (current, &(ctx->variables), variables) { SLIST_FOREACH (current, &(ctx->variables), variables) {
int extra = (strlen(current->value) - strlen(current->key)); const int extra = (strlen(current->value) - strlen(current->key));
for (char *next = bufcopy; for (char *next = bufcopy;
next < (bufcopy + stbuf.st_size) && next < (bufcopy + stbuf.st_size) &&
(next = strcasestr(next, current->key)) != NULL;) { (next = strcasestr(next, current->key)) != NULL;) {
/* We need to invalidate variables completely (otherwise we may count /* We need to invalidate variables completely (otherwise we may count
* the same variable more than once, thus causing buffer overflow or * the same variable more than once, thus causing buffer overflow or
* allocation failure) with spaces (variable names cannot contain spaces) */ * allocation failure) with spaces (variable names cannot contain spaces) */
char *end = next + strlen(current->key); const char *end = next + strlen(current->key);
while (next < end) { while (next < end) {
*next++ = ' '; *next++ = ' ';
} }
@ -870,7 +779,7 @@ static parse_file_result_t parse_file_inner(struct parser_ctx *ctx, const char *
/* Then, allocate a new buffer and copy the file over to the new one, /* Then, allocate a new buffer and copy the file over to the new one,
* but replace occurrences of our variables */ * but replace occurrences of our variables */
char *walk = buf; const char *walk = buf;
char *new = scalloc(stbuf.st_size + extra_bytes + 1, 1); char *new = scalloc(stbuf.st_size + extra_bytes + 1, 1);
char *destwalk = new; char *destwalk = new;
while (walk < (buf + stbuf.st_size)) { while (walk < (buf + stbuf.st_size)) {
@ -878,7 +787,7 @@ static parse_file_result_t parse_file_inner(struct parser_ctx *ctx, const char *
SLIST_FOREACH (current, &(ctx->variables), variables) { SLIST_FOREACH (current, &(ctx->variables), variables) {
current->next_match = strcasestr(walk, current->key); current->next_match = strcasestr(walk, current->key);
} }
struct Variable *nearest = NULL; const struct Variable *nearest = NULL;
int distance = stbuf.st_size; int distance = stbuf.st_size;
SLIST_FOREACH (current, &(ctx->variables), variables) { SLIST_FOREACH (current, &(ctx->variables), variables) {
if (current->next_match == NULL) { if (current->next_match == NULL) {

101
src/parser_util.c Normal file
View File

@ -0,0 +1,101 @@
/*
* vim:ts=4:sw=4:expandtab
*
* i3 - an improved tiling window manager
* © 2009 Michael Stapelberg and contributors (see also: LICENSE)
*
* parser_util.c: utility functions for the config and commands parser
*
*/
#include "all.h"
#include "parser_util.h"
/*
* Pushes a string (identified by 'identifier') on the stack. We simply use a
* single array, since the number of entries we have to store is very small.
*
*/
void parser_push_string(struct stack *stack, const char *identifier, const char *str) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].identifier != NULL &&
strcmp(stack->stack[c].identifier, identifier) != 0) {
continue;
}
if (stack->stack[c].identifier == NULL) {
/* Found a free slot, let's store it here. */
stack->stack[c].identifier = identifier;
stack->stack[c].val.str = sstrdup(str);
stack->stack[c].type = STACK_STR;
} else {
/* Append the value. */
char *prev = stack->stack[c].val.str;
sasprintf(&stack->stack[c].val.str, "%s,%s", prev, str);
free(prev);
}
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means there's either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(EXIT_FAILURE);
}
void parser_push_long(struct stack *stack, const char *identifier, const long num) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].identifier != NULL) {
continue;
}
/* Found a free slot, let's store it here. */
stack->stack[c].identifier = identifier;
stack->stack[c].val.num = num;
stack->stack[c].type = STACK_LONG;
return;
}
/* When we arrive here, the stack is full. This should not happen and
* means there's either a bug in this parser or the specification
* contains a command with more than 10 identified tokens. */
fprintf(stderr, "BUG: parser stack full. This means either a bug "
"in the code, or a new command which contains more than "
"10 identified tokens.\n");
exit(EXIT_FAILURE);
}
const char *parser_get_string(const struct stack *stack, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].identifier == NULL) {
break;
}
if (strcmp(identifier, stack->stack[c].identifier) == 0) {
return stack->stack[c].val.str;
}
}
return NULL;
}
long parser_get_long(const struct stack *stack, const char *identifier) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].identifier == NULL) {
break;
}
if (strcmp(identifier, stack->stack[c].identifier) == 0) {
return stack->stack[c].val.num;
}
}
return 0;
}
void parser_clear_stack(struct stack *stack) {
for (int c = 0; c < 10; c++) {
if (stack->stack[c].type == STACK_STR) {
free(stack->stack[c].val.str);
}
stack->stack[c].identifier = NULL;
stack->stack[c].val.str = NULL;
stack->stack[c].val.num = 0;
}
}