On the rationale of using a custom parser instead of a lex/yacc one, see this quote from src/commands_parser.c: We use a hand-written parser instead of lex/yacc because our commands are easy for humans, not for computers. Thus, it’s quite hard to specify a context-free grammar for the commands. A PEG grammar would be easier, but there’s downsides to every PEG parser generator I have come accross so far. This parser is basically a state machine which looks for literals or strings and can push either on a stack. After identifying a literal or string, it will either transition to the current state, to a different state, or call a function (like cmd_move()). Special care has been taken that error messages are useful and the code is well testable (when compiled with -DTEST_PARSER it will output to stdout instead of actually calling any function). During the migration phase (I plan to completely switch to this parser before 4.2 will be released), the new parser will parse every command you send to i3 and save the resulting call stack. Then, the old parser will parse your input and actually execute the commands. Afterwards, both call stacks will be compared and any differences will be logged. The new parser works with 100% of the test suite and produces identical call stacks.
215 lines
8.8 KiB
Plaintext
215 lines
8.8 KiB
Plaintext
/*
|
|
* vim:ts=4:sw=4:expandtab
|
|
*
|
|
* i3 - an improved dynamic tiling window manager
|
|
* © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE)
|
|
*
|
|
* cmdparse.l: the lexer for commands you send to i3 (or bind on keys)
|
|
*
|
|
*/
|
|
%option nounput
|
|
%option noinput
|
|
%option noyy_top_state
|
|
%option stack
|
|
|
|
%{
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "cmdparse.tab.h"
|
|
|
|
#include "config.h"
|
|
#include "util.h"
|
|
#include "libi3.h"
|
|
|
|
int cmdyycolumn = 1;
|
|
|
|
#define YY_DECL int yylex (struct context *context)
|
|
|
|
#define YY_USER_ACTION { \
|
|
context->first_column = cmdyycolumn; \
|
|
context->last_column = cmdyycolumn+yyleng-1; \
|
|
cmdyycolumn += yyleng; \
|
|
}
|
|
|
|
/* macro to first eat whitespace, then expect a string */
|
|
#define WS_STRING do { \
|
|
yy_push_state(WANT_STRING); \
|
|
yy_push_state(EAT_WHITESPACE); \
|
|
} while (0)
|
|
|
|
%}
|
|
|
|
EOL (\r?\n)
|
|
|
|
/* handle everything up to \n as a string */
|
|
%s WANT_STRING
|
|
/* eat a whitespace, then go to the next state on the stack */
|
|
%s EAT_WHITESPACE
|
|
/* handle a quoted string or everything up to the next whitespace */
|
|
%s WANT_QSTRING
|
|
|
|
%x MOVE
|
|
%x MOVE_WS
|
|
|
|
%x EXEC
|
|
|
|
%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 = sstrdup(yytext);
|
|
|
|
yyless(0);
|
|
yy_pop_state();
|
|
yy_set_bol(true);
|
|
cmdyycolumn = 1;
|
|
}
|
|
|
|
/* The next/prev/back_and_forth tokens are here to recognize them *before*
|
|
* handling strings ('workspace' command). While flex uses the longest
|
|
* match, in case of a tie the order of rules becomes relevant. Since the
|
|
* input is fully consumed (these are the last tokens), it comes to a tie.
|
|
* */
|
|
next { BEGIN(INITIAL); return TOK_NEXT; }
|
|
prev { BEGIN(INITIAL); return TOK_PREV; }
|
|
next_on_output { BEGIN(INITIAL); return TOK_NEXT_ON_OUTPUT; }
|
|
prev_on_output { BEGIN(INITIAL); return TOK_PREV_ON_OUTPUT; }
|
|
back_and_forth { BEGIN(INITIAL); return TOK_BACK_AND_FORTH; }
|
|
|
|
/* MOVE is the state after a 'move' token was processed. We need this state
|
|
* to skip some tokens (for making the commands clearer) and to properly
|
|
* move to the MOVE_WS state. */
|
|
<MOVE>to { /* eat this token */ }
|
|
<MOVE>window { /* eat this token */ }
|
|
<MOVE>container { /* eat this token */ }
|
|
<MOVE>workspace { yy_pop_state(); yy_push_state(MOVE_WS); yy_push_state(EAT_WHITESPACE); return TOK_WORKSPACE; }
|
|
<MOVE>scratchpad { yy_pop_state(); return TOK_SCRATCHPAD; }
|
|
<MOVE>output { yy_pop_state(); return TOK_OUTPUT; }
|
|
<MOVE>up { yy_pop_state(); return TOK_UP; }
|
|
<MOVE>down { yy_pop_state(); return TOK_DOWN; }
|
|
<MOVE>left { yy_pop_state(); return TOK_LEFT; }
|
|
<MOVE>right { yy_pop_state(); return TOK_RIGHT; }
|
|
|
|
/* MOVE_WS is the state after a 'workspace' token was processed in the MOVE
|
|
* state. We need a separate state to deal with the fact that the old
|
|
* 'move workspace <ws>' command needs to be supported (the new command is
|
|
* 'move to workspace') while we also need to support
|
|
* 'move workspace to output <output>'. */
|
|
<MOVE_WS>to { yy_pop_state(); return TOK_TO; }
|
|
<MOVE_WS>[^t] { yy_pop_state(); yy_push_state(WANT_STRING); yyless(0); }
|
|
|
|
<WANT_STRING>\"[^\"]+\" {
|
|
BEGIN(INITIAL);
|
|
/* strip quotes */
|
|
char *copy = sstrdup(yytext+1);
|
|
copy[strlen(copy)-1] = '\0';
|
|
cmdyylval.string = copy;
|
|
return STR;
|
|
}
|
|
<WANT_QSTRING>\"[^\"]+\" {
|
|
BEGIN(INITIAL);
|
|
/* strip quotes */
|
|
char *copy = sstrdup(yytext+1);
|
|
copy[strlen(copy)-1] = '\0';
|
|
cmdyylval.string = copy;
|
|
return STR;
|
|
}
|
|
|
|
<WANT_STRING>[^;\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; }
|
|
|
|
<EAT_WHITESPACE>[;\n] { BEGIN(INITIAL); return ';'; }
|
|
<EAT_WHITESPACE>[ \t]* { yy_pop_state(); }
|
|
|
|
[ \t]* { /* ignore whitespace */ ; }
|
|
<EXEC>--no-startup-id { printf("no startup id\n"); yy_pop_state(); return TOK_NO_STARTUP_ID; }
|
|
<EXEC>. { printf("anything else: *%s*\n", yytext); yyless(0); yy_pop_state(); yy_pop_state(); }
|
|
exec { WS_STRING; yy_push_state(EXEC); yy_push_state(EAT_WHITESPACE); return TOK_EXEC; }
|
|
exit { return TOK_EXIT; }
|
|
reload { return TOK_RELOAD; }
|
|
restart { return TOK_RESTART; }
|
|
kill { return TOK_KILL; }
|
|
window { return TOK_WINDOW; }
|
|
container { return TOK_CONTAINER; }
|
|
client { return TOK_CLIENT; }
|
|
fullscreen { return TOK_FULLSCREEN; }
|
|
global { return TOK_GLOBAL; }
|
|
layout { return TOK_LAYOUT; }
|
|
default { return TOK_DEFAULT; }
|
|
stacked { return TOK_STACKED; }
|
|
stacking { return TOK_STACKED; }
|
|
tabbed { return TOK_TABBED; }
|
|
border { return TOK_BORDER; }
|
|
normal { return TOK_NORMAL; }
|
|
none { return TOK_NONE; }
|
|
1pixel { return TOK_1PIXEL; }
|
|
mode { BEGIN(WANT_QSTRING); return TOK_MODE; }
|
|
tiling { return TOK_TILING; }
|
|
floating { return TOK_FLOATING; }
|
|
toggle { return TOK_TOGGLE; }
|
|
mode_toggle { return TOK_MODE_TOGGLE; }
|
|
workspace { WS_STRING; return TOK_WORKSPACE; }
|
|
output { WS_STRING; return TOK_OUTPUT; }
|
|
focus { return TOK_FOCUS; }
|
|
move { yy_push_state(MOVE); return TOK_MOVE; }
|
|
open { return TOK_OPEN; }
|
|
scratchpad { return TOK_SCRATCHPAD; }
|
|
show { return TOK_SHOW; }
|
|
split { return TOK_SPLIT; }
|
|
horizontal { return TOK_HORIZONTAL; }
|
|
vertical { return TOK_VERTICAL; }
|
|
up { return TOK_UP; }
|
|
down { return TOK_DOWN; }
|
|
left { return TOK_LEFT; }
|
|
right { return TOK_RIGHT; }
|
|
parent { return TOK_PARENT; }
|
|
child { return TOK_CHILD; }
|
|
resize { return TOK_RESIZE; }
|
|
shrink { return TOK_SHRINK; }
|
|
grow { return TOK_GROW; }
|
|
px { return TOK_PX; }
|
|
or { return TOK_OR; }
|
|
ppt { return TOK_PPT; }
|
|
to { return TOK_TO; }
|
|
nop { WS_STRING; return TOK_NOP; }
|
|
append_layout { WS_STRING; return TOK_APPEND_LAYOUT; }
|
|
mark { WS_STRING; return TOK_MARK; }
|
|
|
|
enable { return TOK_ENABLE; }
|
|
true { return TOK_ENABLE; }
|
|
yes { return TOK_ENABLE; }
|
|
disable { return TOK_DISABLE; }
|
|
false { return TOK_DISABLE; }
|
|
no { return TOK_DISABLE; }
|
|
|
|
class { BEGIN(WANT_QSTRING); return TOK_CLASS; }
|
|
instance { BEGIN(WANT_QSTRING); return TOK_INSTANCE; }
|
|
window_role { BEGIN(WANT_QSTRING); return TOK_WINDOW_ROLE; }
|
|
id { BEGIN(WANT_QSTRING); return TOK_ID; }
|
|
con_id { BEGIN(WANT_QSTRING); return TOK_CON_ID; }
|
|
con_mark { BEGIN(WANT_QSTRING); return TOK_MARK; }
|
|
title { BEGIN(WANT_QSTRING); return TOK_TITLE; }
|
|
|
|
[0-9]+ { cmdyylval.number = atoi(yytext); return NUMBER; }
|
|
|
|
. { return (int)yytext[0]; }
|
|
|
|
<<EOF>> {
|
|
while (yy_start_stack_ptr > 0)
|
|
yy_pop_state();
|
|
yyterminate();
|
|
}
|
|
|
|
%%
|