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';
my $fmt = $cmd;
# Replace the references to identified literals (like $workspace) with
# calls to get_string(). Also replaces state names (like FOR_WINDOW)
# with their ID (useful for cfg_criteria_init(FOR_WINDOW) e.g.).
# calls to parser_get_string(). Also replaces state names (like
# FOR_WINDOW) with their ID (useful for cfg_criteria_init(FOR_WINDOW)
# e.g.).
$cmd =~ s/$_/$statenum{$_}/g for @keys;
$cmd =~ s/\$([a-z_]+)/get_string(stack, "$1")/g;
$cmd =~ s/\&([a-z_]+)/get_long(stack, "$1")/g;
$cmd =~ s/\$([a-z_]+)/parser_get_string(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
# 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

View File

@@ -43,7 +43,7 @@ CFGFUN(include, const char *pattern);
CFGFUN(font, const char *font);
CFGFUN(exec, const char *exectype, const char *no_startup_id, 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_gaps, const char *enable);
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(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(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_socket_path, const char *socket_path);
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_status_command, const char *command);
CFGFUN(bar_workspace_command, const char *command);

View File

@@ -13,26 +13,11 @@
#include <yajl/yajl_gen.h>
#include "parser_util.h"
SLIST_HEAD(variables_head, Variable);
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 {
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/move.c',
'src/output.c',
'src/parser_util.c',
'src/randr.c',
'src/regex.c',
'src/render.c',
@@ -685,6 +686,7 @@ executable(
'test.commands_parser',
[
'src/commands_parser.c',
'src/parser_util.c',
command_parser,
],
include_directories: inc,
@@ -697,6 +699,7 @@ executable(
'test.config_parser',
[
'src/config_parser.c',
'src/parser_util.c',
config_parser,
],
include_directories: inc,

View File

@@ -24,6 +24,7 @@
*
*/
#include "all.h"
#include "parser_util.h"
// 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)
@@ -56,93 +57,6 @@ typedef struct tokenptr {
#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.
******************************************************************************/
@@ -171,13 +85,13 @@ static void next_state(const cmdp_token *token) {
if (subcommand_output.needs_tree_render) {
command_output.needs_tree_render = true;
}
clear_stack(&stack);
parser_clear_stack(&stack);
return;
}
state = token->next_state;
if (state == INITIAL) {
clear_stack(&stack);
parser_clear_stack(&stack);
}
}
@@ -187,7 +101,7 @@ static void next_state(const cmdp_token *token) {
* 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;
/* Handle quoted strings (or words). */
if (**walk == '"') {
@@ -284,7 +198,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
walk++;
}
cmdp_token_ptr *ptr = &(tokens[state]);
const cmdp_token_ptr *ptr = &(tokens[state]);
token_handled = false;
for (c = 0; c < ptr->n; 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 (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
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;
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. */
char *end = NULL;
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)) ||
(errno != 0 && num == 0)) {
continue;
@@ -319,7 +233,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
}
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 */
@@ -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'));
if (str != NULL) {
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
* double quote. */
if (*walk == '"') {
@@ -441,7 +356,7 @@ CommandResult *parse_command(const char *input, yajl_gen gen, ipc_client *client
y(map_close);
free(position);
clear_stack(&stack);
parser_clear_stack(&stack);
break;
}
}

View File

@@ -52,8 +52,7 @@ CFGFUN(include, const char *pattern) {
file->path = sstrdup(resolved_path);
TAILQ_INSERT_TAIL(&included_files, file, files);
struct stack stack;
memset(&stack, '\0', sizeof(struct stack));
struct stack stack = {0};
struct parser_ctx ctx = {
.use_nagbar = result->ctx->use_nagbar,
.stack = &stack,
@@ -79,7 +78,6 @@ CFGFUN(include, const char *pattern) {
default:
/* missing case statement */
assert(false);
break;
}
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.
*
*/
CFGFUN(criteria_init, int _state) {
CFGFUN(criteria_init, const int _state) {
criteria_next_state = _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);
}
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) {
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) {
int pixels = logical_px(value);
gaps_mask_t mask = gaps_scope_to_mask(scope);
const int pixels = logical_px(value);
const gaps_mask_t mask = gaps_scope_to_mask(scope);
if (workspace == NULL) {
apply_gaps(&config.gaps, mask, pixels);
@@ -536,9 +534,9 @@ CFGFUN(workspace, const char *workspace, const char *output) {
struct Workspace_Assignment *assignment;
/* When a new workspace line is encountered, for the first output word,
* $workspace from the config.spec is non-NULL. Afterwards, the parser calls
* clear_stack() because of the call line. Thus, we have to preserve the
* workspace string. */
* $workspace from the config.spec is non-NULL. Afterward, the parser calls
* parser_clear_stack() because of the call line. Thus, we have to preserve
* the workspace string. */
if (workspace) {
FREE(current_workspace);
@@ -654,7 +652,7 @@ CFGFUN(assign_output, const char *output) {
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) {
ELOG("match has error: %s\n", current_match->error);
return;
@@ -746,7 +744,7 @@ CFGFUN(bar_id, const char *bar_id) {
}
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[current_bar->num_outputs] = sstrdup(output);
current_bar->num_outputs = new_outputs;
@@ -810,7 +808,7 @@ static void bar_configure_binding(const char *button, const char *release, const
return;
}
int input_code = atoi(button + strlen("button"));
const int input_code = atoi(button + strlen("button"));
if (input_code < 1) {
ELOG("Button \"%s\" does not seem to be in format 'buttonX'.\n", button);
return;

View File

@@ -72,96 +72,6 @@ typedef struct tokenptr {
#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.
******************************************************************************/
@@ -180,12 +90,12 @@ static void next_state(const cmdp_token *token, struct parser_ctx *ctx) {
ctx->has_errors = true;
}
_next_state = subcommand_output.next_state;
clear_stack(ctx->stack);
parser_clear_stack(ctx->stack);
}
ctx->state = _next_state;
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
@@ -236,7 +146,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
const char *dumpwalk = input;
int linecnt = 1;
while (*dumpwalk != '\0') {
char *next_nl = strchr(dumpwalk, '\n');
const char *next_nl = strchr(dumpwalk, '\n');
if (next_nl != NULL) {
DLOG("CONFIG(line %3d): %.*s\n", linecnt, (int)(next_nl - dumpwalk), dumpwalk);
dumpwalk = next_nl + 1;
@@ -275,7 +185,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
walk++;
}
cmdp_token_ptr *ptr = &(tokens[ctx->state]);
const cmdp_token_ptr *ptr = &(tokens[ctx->state]);
token_handled = false;
for (c = 0; c < ptr->n; 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 (strncasecmp(walk, token->name + 1, strlen(token->name) - 1) == 0) {
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;
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. */
char *end = NULL;
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)) ||
(errno != 0 && num == 0)) {
continue;
@@ -310,7 +220,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
}
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 */
@@ -363,7 +273,7 @@ static void parse_config(struct parser_ctx *ctx, const char *input, struct conte
str[outpos] = beginning[inpos];
}
if (token->identifier) {
push_string(ctx->stack, token->identifier, str);
parser_push_string(ctx->stack, token->identifier, str);
}
free(str);
/* 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);
/* Print context lines *after* the error, if any. */
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') {
error_line = error_line_end + 1;
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(errormessage);
clear_stack(ctx->stack);
parser_clear_stack(ctx->stack);
/* To figure out in which state to go (e.g. MODE or INITIAL),
* we find the nearest state which contains an <error> token
* and follow that one. */
bool error_token_found = false;
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++) {
if (strcmp(errptr->array[j].name, "error") != 0) {
continue;
@@ -594,7 +504,7 @@ int main(int argc, char *argv[]) {
/**
* 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;
sasprintf(&editaction, "i3-sensible-editor \"%s\" && i3-msg reload\n", configpath);
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) {
struct Variable *current;
while (!SLIST_EMPTY(&(ctx->variables))) {
current = SLIST_FIRST(&(ctx->variables));
struct Variable *current = SLIST_FIRST(&(ctx->variables));
FREE(current->key);
FREE(current->value);
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) */
char *bufcopy = sstrdup(buf);
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;
next < (bufcopy + stbuf.st_size) &&
(next = strcasestr(next, current->key)) != NULL;) {
/* We need to invalidate variables completely (otherwise we may count
* the same variable more than once, thus causing buffer overflow or
* 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) {
*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,
* but replace occurrences of our variables */
char *walk = buf;
const char *walk = buf;
char *new = scalloc(stbuf.st_size + extra_bytes + 1, 1);
char *destwalk = new;
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) {
current->next_match = strcasestr(walk, current->key);
}
struct Variable *nearest = NULL;
const struct Variable *nearest = NULL;
int distance = stbuf.st_size;
SLIST_FOREACH (current, &(ctx->variables), variables) {
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;
}
}