lexer/parser: proper error messages

Error messages now look like this:

13.02.2010 19:42:30 - ERROR:
13.02.2010 19:42:30 - ERROR: CONFIG: syntax error, unexpected <word>,
expecting default/stacking/tabbed or stack-limit
13.02.2010 19:42:30 - ERROR: CONFIG: in file "inv", line 15:
13.02.2010 19:42:30 - ERROR: CONFIG:   new_container foobar
13.02.2010 19:42:30 - ERROR: CONFIG:                 ^^^^^^
13.02.2010 19:42:30 - ERROR:
This commit is contained in:
Michael Stapelberg 2010-02-13 19:42:54 +01:00
parent 01297af20a
commit 64cf88403d
4 changed files with 112 additions and 16 deletions

View File

@ -3,7 +3,7 @@
* *
* i3 - an improved dynamic tiling window manager * i3 - an improved dynamic tiling window manager
* *
* © 2009 Michael Stapelberg and contributors * © 2009-2010 Michael Stapelberg and contributors
* *
* See file LICENSE for license information. * See file LICENSE for license information.
* *
@ -25,6 +25,21 @@ typedef struct Config Config;
extern Config config; extern Config config;
extern SLIST_HEAD(modes_head, Mode) modes; extern SLIST_HEAD(modes_head, Mode) modes;
/**
* Used during the config file lexing/parsing to keep the state of the lexer
* in order to provide useful error messages in yyerror().
*
*/
struct context {
int line_number;
char *line_copy;
const char *filename;
/* These are the same as in YYLTYPE */
int first_column;
int last_column;
};
/** /**
* Part of the struct Config. It makes sense to group colors for background, * Part of the struct Config. It makes sense to group colors for background,
* border and text as every element in i3 has them (window decorations, bar). * border and text as every element in i3 has them (window decorations, bar).

View File

@ -1,5 +1,7 @@
%option nounput %option nounput
%option noinput %option noinput
%option noyy_top_state
%option stack
%{ %{
/* /*
@ -13,19 +15,57 @@
#include "data.h" #include "data.h"
#include "config.h" #include "config.h"
#include "log.h"
#include "util.h"
int yycolumn = 1;
#define YY_DECL int yylex (struct context *context)
#define YY_USER_ACTION { \
context->first_column = yycolumn; \
context->last_column = yycolumn+yyleng-1; \
yycolumn += yyleng; \
}
%} %}
%Start BIND_COND EOL (\r?\n)
%Start BINDSYM_COND
%Start BIND_AWS_COND %s BIND_COND
%Start BINDSYM_AWS_COND %s BINDSYM_COND
%Start BIND_A2WS_COND %s BIND_AWS_COND
%Start ASSIGN_COND %s BINDSYM_AWS_COND
%Start COLOR_COND %s BIND_A2WS_COND
%Start SCREEN_COND %s ASSIGN_COND
%Start SCREEN_AWS_COND %s COLOR_COND
%s SCREEN_COND
%s SCREEN_AWS_COND
%x BUFFER_LINE
%% %%
{
/* This is called when a new line is lexed. We only want the
* first line to match to go into state BUFFER_LINE */
if (context->line_number == 0) {
context->line_number = 1;
BEGIN(INITIAL);
yy_push_state(BUFFER_LINE);
}
}
<BUFFER_LINE>^[^\r\n]*/{EOL}? {
/* save whole line */
context->line_copy = strdup(yytext);
yyless(0);
yy_pop_state();
yy_set_bol(true);
yycolumn = 1;
}
<BIND_A2WS_COND>[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; } <BIND_A2WS_COND>[^\n]+ { BEGIN(INITIAL); yylval.string = strdup(yytext); return STR; }
^[ \t]*#[^\n]* { return TOKCOMMENT; } ^[ \t]*#[^\n]* { return TOKCOMMENT; }
<COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; } <COLOR_COND>[0-9a-fA-F]+ { yylval.string = strdup(yytext); return HEX; }
@ -69,7 +109,11 @@ control { return TOKCONTROL; }
ctrl { return TOKCONTROL; } ctrl { return TOKCONTROL; }
shift { return TOKSHIFT; } shift { return TOKSHIFT; }
→ { return TOKARROW; } → { return TOKARROW; }
\n /* ignore end of line */; {EOL} {
FREE(context->line_copy);
context->line_number++;
yy_push_state(BUFFER_LINE);
}
<SCREEN_AWS_COND>x { return (int)yytext[0]; } <SCREEN_AWS_COND>x { return (int)yytext[0]; }
<BIND_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; } <BIND_COND>[ \t]+ { BEGIN(BIND_AWS_COND); return WHITESPACE; }
<BINDSYM_COND>[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; } <BINDSYM_COND>[ \t]+ { BEGIN(BINDSYM_AWS_COND); return WHITESPACE; }
@ -91,4 +135,11 @@ shift { return TOKSHIFT; }
<BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; } <BINDSYM_AWS_COND>[a-zA-Z0-9_]+ { yylval.string = strdup(yytext); return WORD; }
[a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; } [a-zA-Z]+ { yylval.string = strdup(yytext); return WORD; }
. { return (int)yytext[0]; } . { return (int)yytext[0]; }
<<EOF>> {
while (yy_start_stack_ptr > 0)
yy_pop_state();
yyterminate();
}
%% %%

View File

@ -24,17 +24,32 @@
#include "log.h" #include "log.h"
typedef struct yy_buffer_state *YY_BUFFER_STATE; typedef struct yy_buffer_state *YY_BUFFER_STATE;
extern int yylex(void); extern int yylex(struct context *context);
extern int yyparse(void); extern int yyparse(void);
extern FILE *yyin; extern FILE *yyin;
YY_BUFFER_STATE yy_scan_string(const char *); YY_BUFFER_STATE yy_scan_string(const char *);
static struct bindings_head *current_bindings; static struct bindings_head *current_bindings;
static struct context *context;
int yydebug = 1; /* We dont need yydebug for now, as we got decent error messages using
* yyerror(). Should you ever want to extend the parser, it might be handy
* to just comment it in again, so it stays here. */
//int yydebug = 1;
void yyerror(const char *str) { void yyerror(const char *error_message) {
fprintf(stderr,"error: %s\n",str); ELOG("\n");
ELOG("CONFIG: %s\n", error_message);
ELOG("CONFIG: in file \"%s\", line %d:\n",
context->filename, context->line_number);
ELOG("CONFIG: %s\n", context->line_copy);
ELOG("CONFIG: ");
for (int c = 1; c <= context->last_column; c++)
if (c >= context->first_column)
printf("^");
else printf(" ");
printf("\n");
ELOG("\n");
} }
int yywrap() { int yywrap() {
@ -149,11 +164,16 @@ void parse_file(const char *f) {
yy_scan_string(new); yy_scan_string(new);
context = scalloc(sizeof(struct context));
context->filename = f;
if (yyparse() != 0) { if (yyparse() != 0) {
fprintf(stderr, "Could not parse configfile\n"); fprintf(stderr, "Could not parse configfile\n");
exit(1); exit(1);
} }
FREE(context->line_copy);
free(context);
free(new); free(new);
free(buf); free(buf);
} }
@ -162,6 +182,7 @@ void parse_file(const char *f) {
%expect 1 %expect 1
%error-verbose %error-verbose
%lex-param { struct context *context }
%union { %union {
int number; int number;

View File

@ -150,6 +150,7 @@ int main(int argc, char *argv[], char *env[]) {
int i, screens, opt; int i, screens, opt;
char *override_configpath = NULL; char *override_configpath = NULL;
bool autostart = true; bool autostart = true;
bool only_check_config = false;
xcb_connection_t *conn; xcb_connection_t *conn;
xcb_property_handlers_t prophs; xcb_property_handlers_t prophs;
xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS];
@ -170,7 +171,7 @@ int main(int argc, char *argv[], char *env[]) {
start_argv = argv; start_argv = argv;
while ((opt = getopt_long(argc, argv, "c:vahld:V", long_options, &option_index)) != -1) { while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) {
switch (opt) { switch (opt) {
case 'a': case 'a':
LOG("Autostart disabled using -a\n"); LOG("Autostart disabled using -a\n");
@ -179,6 +180,10 @@ int main(int argc, char *argv[], char *env[]) {
case 'c': case 'c':
override_configpath = sstrdup(optarg); override_configpath = sstrdup(optarg);
break; break;
case 'C':
LOG("Checking configuration file only (-C)\n");
only_check_config = true;
break;
case 'v': case 'v':
printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n");
exit(EXIT_SUCCESS); exit(EXIT_SUCCESS);
@ -218,6 +223,10 @@ int main(int argc, char *argv[], char *env[]) {
die("Cannot open display\n"); die("Cannot open display\n");
load_configuration(conn, override_configpath, false); load_configuration(conn, override_configpath, false);
if (only_check_config) {
LOG("Done checking configuration file. Exiting.\n");
exit(0);
}
/* Create the initial container on the first workspace. This used to /* Create the initial container on the first workspace. This used to
* be part of init_table, but since it possibly requires an X * be part of init_table, but since it possibly requires an X