From 769501420ddeee0f808b3fd01b0463f426e9ef43 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Wed, 14 Apr 2010 20:26:56 +0200 Subject: [PATCH] add first version of a new flex/bison based command parser --- Makefile | 16 ++- include/con.h | 1 + src/cmdparse.l | 112 ++++++++++++++++++++ src/cmdparse.y | 274 +++++++++++++++++++++++++++++++++++++++++++++++++ src/con.c | 2 +- src/nc.c | 13 ++- 6 files changed, 413 insertions(+), 5 deletions(-) create mode 100644 src/cmdparse.l create mode 100644 src/cmdparse.y diff --git a/Makefile b/Makefile index 810a7863..368dc777 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TOPDIR=$(shell pwd) include $(TOPDIR)/common.mk # Depend on the object files of all source-files in src/*.c and on all header files -AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c +AUTOGENERATED:=src/cfgparse.tab.c src/cfgparse.yy.c src/cmdparse.tab.c src/cmdparse.yy.c FILES:=src/ipc.c src/nc.c src/log.c src/util.c src/tree.c src/xcb.c src/manage.c src/workspace.c src/x.c src/floating.c src/click.c src/config.c src/handlers.c src/randr.c src/xinerama.c src/con.c src/load_layout.c src/render.c src/window.c FILES:=$(FILES:.c=.o) HEADERS:=$(filter-out include/loglevels.h,$(wildcard include/*.h)) @@ -23,7 +23,7 @@ src/%.o: src/%.c ${HEADERS} echo "CC $<" $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/$(shell basename $< .c)/ { print NR }' loglevels.tmp))" -c -o $@ $< -all: src/cfgparse.y.o src/cfgparse.yy.o ${FILES} +all: src/cfgparse.y.o src/cfgparse.yy.o src/cmdparse.y.o src/cmdparse.yy.o ${FILES} echo "LINK i3" $(CC) -o i3 $^ $(LDFLAGS) @@ -44,11 +44,23 @@ src/cfgparse.yy.o: src/cfgparse.l src/cfgparse.y.o ${HEADERS} flex -i -o$(@:.o=.c) $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) +src/cmdparse.yy.o: src/cmdparse.l src/cmdparse.y.o ${HEADERS} + echo "LEX $<" + flex -P cmdyy -i -o$(@:.o=.c) $< + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.l/ { print NR }' loglevels.tmp))" -c -o $@ $(@:.o=.c) + + src/cfgparse.y.o: src/cfgparse.y ${HEADERS} echo "YACC $<" bison --debug --verbose -b $(basename $< .y) -d $< $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cfgparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) +src/cmdparse.y.o: src/cmdparse.y ${HEADERS} + echo "YACC $<" + bison -p cmdyy --debug --verbose -b $(basename $< .y) -d $< + $(CC) $(CFLAGS) -DLOGLEVEL="(1 << $(shell awk '/cmdparse.y/ { print NR }' loglevels.tmp))" -c -o $@ $(<:.y=.tab.c) + + install: all echo "INSTALL" $(INSTALL) -d -m 0755 $(DESTDIR)$(PREFIX)/bin diff --git a/include/con.h b/include/con.h index 9b14d897..71f73e3d 100644 --- a/include/con.h +++ b/include/con.h @@ -13,6 +13,7 @@ Con *con_by_frame_id(xcb_window_t frame); Con *con_for_window(i3Window *window, Match **store_match); void con_attach(Con *con, Con *parent); void con_detach(Con *con); +bool match_matches_window(Match *match, i3Window *window); enum { WINDOW_ADD = 0, WINDOW_REMOVE = 1 }; void con_fix_percent(Con *con, int action); diff --git a/src/cmdparse.l b/src/cmdparse.l new file mode 100644 index 00000000..684a588c --- /dev/null +++ b/src/cmdparse.l @@ -0,0 +1,112 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2010 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 +#include +#include "cmdparse.tab.h" + +#include "config.h" +#include "util.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; \ +} + +%} + +EOL (\r?\n) + +/* handle everything up to \n as a string */ +%s WANT_STRING +/* first expect a whitespace, then a string */ +%s WANT_WS_STRING +/* handle a quoted string or everything up to the next whitespace */ +%s WANT_QSTRING + +%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); + } + } + +^[^\r\n]*/{EOL}? { + /* save whole line */ + context->line_copy = sstrdup(yytext); + + yyless(0); + yy_pop_state(); + yy_set_bol(true); + cmdyycolumn = 1; +} + +[^\n]+ { BEGIN(INITIAL); cmdyylval.string = sstrdup(yytext); return STR; } +[ \t]* { BEGIN(WANT_STRING); return WHITESPACE; } +\"[^\"]+\" { + BEGIN(INITIAL); + /* strip quotes */ + char *copy = sstrdup(yytext+1); + copy[strlen(copy)-1] = '\0'; + cmdyylval.string = copy; + return STR; + } + +[ \t]* { return WHITESPACE; } +attach { return TOK_ATTACH; } +exec { BEGIN(WANT_WS_STRING); return TOK_EXEC; } +exit { return TOK_EXIT; } +reload { return TOK_RELOAD; } +restart { return TOK_RESTART; } +kill { return TOK_KILL; } +fullscreen { return TOK_FULLSCREEN; } +global { return TOK_GLOBAL; } +layout { return TOK_LAYOUT; } +default { return TOK_DEFAULT; } +stacked { return TOK_STACKED; } +tabbed { return TOK_TABBED; } +border { return TOK_BORDER; } +none { return TOK_NONE; } +1pixel { return TOK_1PIXEL; } +mode { return TOK_MODE; } +tiling { return TOK_TILING; } +floating { return TOK_FLOATING; } +workspace { return TOK_WORKSPACE; } +focus { return TOK_FOCUS; } +move { return TOK_MOVE; } + +class { BEGIN(WANT_QSTRING); return TOK_CLASS; } + +. { return (int)yytext[0]; } + +<> { + while (yy_start_stack_ptr > 0) + yy_pop_state(); + yyterminate(); +} + +%% diff --git a/src/cmdparse.y b/src/cmdparse.y new file mode 100644 index 00000000..eb5ab71f --- /dev/null +++ b/src/cmdparse.y @@ -0,0 +1,274 @@ +%{ +/* + * vim:ts=4:sw=4:expandtab + * + * i3 - an improved dynamic tiling window manager + * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE) + * + * cmdparse.y: the parser for commands you send to i3 (or bind on keys) + * + + */ +#include +#include +#include +#include + +#include "all.h" + +typedef struct yy_buffer_state *YY_BUFFER_STATE; +extern int cmdyylex(struct context *context); +extern int cmdyyparse(void); +extern FILE *cmdyyin; +YY_BUFFER_STATE cmdyy_scan_string(const char *); + +static struct bindings_head *current_bindings; +static struct context *context; +static Match current_match; + +/* + * Helper data structure for an operation window (window on which the operation + * will be performed). Used to build the TAILQ owindows. + * + */ +typedef struct owindow { + Con *con; + TAILQ_ENTRY(owindow) owindows; +} owindow; +static TAILQ_HEAD(owindows_head, owindow) owindows; + +/* We don’t 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 cmdyydebug = 1; + +void cmdyyerror(const char *error_message) { + ELOG("\n"); + ELOG("CMD: %s\n", error_message); + ELOG("CMD: in file \"%s\", line %d:\n", + context->filename, context->line_number); + ELOG("CMD: %s\n", context->line_copy); + ELOG("CMD: "); + for (int c = 1; c <= context->last_column; c++) + if (c >= context->first_column) + printf("^"); + else printf(" "); + printf("\n"); + ELOG("\n"); +} + +int cmdyywrap() { + return 1; +} + +void parse_cmd(const char *new) { + + //const char *new = "[level-up workspace] attach $output, focus"; + + cmdyy_scan_string(new); + + context = scalloc(sizeof(struct context)); + context->filename = "cmd"; + if (cmdyyparse() != 0) { + fprintf(stderr, "Could not parse configfile\n"); + exit(1); + } + printf("done\n"); + + FREE(context->line_copy); + free(context); +} + +%} + +%error-verbose +%lex-param { struct context *context } + +%union { + char *string; +} + +%token TOK_ATTACH "attach" +%token TOK_EXEC "exec" +%token TOK_EXIT "exit" +%token TOK_RELOAD "reload" +%token TOK_RESTART "restart" +%token TOK_KILL "kill" +%token TOK_FULLSCREEN "fullscreen" +%token TOK_GLOBAL "global" +%token TOK_LAYOUT "layout" +%token TOK_DEFAULT "default" +%token TOK_STACKED "stacked" +%token TOK_TABBED "tabbed" +%token TOK_BORDER "border" +%token TOK_NONE "none" +%token TOK_1PIXEL "1pixel" +%token TOK_MODE "mode" +%token TOK_TILING "tiling" +%token TOK_FLOATING "floating" +%token TOK_WORKSPACE "workspace" +%token TOK_FOCUS "focus" +%token TOK_MOVE "move" + +%token TOK_CLASS "class" + +%token WHITESPACE "" +%token STR "" + +%% + +commands: /* empty */ + | commands optwhitespace ';' optwhitespace command + | command + { + owindow *current; + + printf("single command completely parsed, dropping state...\n"); + while (!TAILQ_EMPTY(&owindows)) { + current = TAILQ_FIRST(&owindows); + TAILQ_REMOVE(&owindows, current, owindows); + free(current); + } + } + ; + +optwhitespace: + | WHITESPACE + ; + +command: + match optwhitespace operations + ; + +match: + | matchstart optwhitespace criteria optwhitespace matchend + { + printf("match parsed\n"); + } + ; + +matchstart: + '[' + { + printf("start\n"); + memset(¤t_match, '\0', sizeof(Match)); + TAILQ_INIT(&owindows); + /* copy all_cons */ + Con *con; + TAILQ_FOREACH(con, &all_cons, all_cons) { + if (con->window == NULL) + continue; + + owindow *ow = smalloc(sizeof(owindow)); + ow->con = con; + TAILQ_INSERT_TAIL(&owindows, ow, owindows); + } + } + ; + +matchend: + ']' + { + owindow *next, *current; + + printf("match specification finished, matching...\n"); + /* copy the old list head to iterate through it and start with a fresh + * list which will contain only matching windows */ + struct owindows_head old = owindows; + TAILQ_INIT(&owindows); + for (next = TAILQ_FIRST(&old); next != TAILQ_END(&old);) { + /* make a copy of the next pointer and advance the pointer to the + * next element as we are going to invalidate the element’s + * next/prev pointers by calling TAILQ_INSERT_TAIL later */ + current = next; + next = TAILQ_NEXT(next, owindows); + + printf("checking if con %p / %s matches\n", current->con, current->con->name); + if (match_matches_window(¤t_match, current->con->window)) { + printf("matches!\n"); + TAILQ_INSERT_TAIL(&owindows, current, owindows); + } else { + printf("doesnt match\n"); + free(current); + } + } + + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + } + + } + ; + +criteria: + TOK_CLASS '=' STR + { + printf("criteria: class = %s\n", $3); + current_match.class = $3; + } + ; + +operations: + operation + | operation optwhitespace + | operations ',' optwhitespace operation + ; + +operation: + exec + | exit + /*| reload + | restart + | mark + | fullscreen + | layout + | border + | mode + | workspace + | move*/ + | attach + | focus + | kill + ; + +exec: + TOK_EXEC WHITESPACE STR + { + printf("should execute %s\n", $3); + } + ; + +exit: + TOK_EXIT + { + printf("exit, bye bye\n"); + } + ; + +attach: + TOK_ATTACH + { + printf("should attach\n"); + } + ; + +focus: + TOK_FOCUS + { + printf("should focus\n"); + } + ; + +kill: + TOK_KILL + { + owindow *current; + + printf("killing!\n"); + TAILQ_FOREACH(current, &owindows, owindows) { + printf("matching: %p / %s\n", current->con, current->con->name); + tree_close(current->con); + } + + } + ; diff --git a/src/con.c b/src/con.c index 75440973..5c220458 100644 --- a/src/con.c +++ b/src/con.c @@ -203,7 +203,7 @@ Con *con_by_frame_id(xcb_window_t frame) { return NULL; } -static bool match_matches_window(Match *match, i3Window *window) { +bool match_matches_window(Match *match, i3Window *window) { /* TODO: pcre, full matching, … */ if (match->class != NULL && strcasecmp(match->class, window->class_class) == 0) { LOG("match made by window class (%s)\n", window->class_class); diff --git a/src/nc.c b/src/nc.c index 2b523ee5..92dba08d 100644 --- a/src/nc.c +++ b/src/nc.c @@ -109,8 +109,16 @@ void parse_command(const char *command) { start_application(command + strlen("exec ")); else if (strcasecmp(command, "restart") == 0) i3_restart(); - else if (strcasecmp(command, "floating") == 0) - toggle_floating_mode(focused, false); + else if (strcasecmp(command, "floating") == 0) { + //toggle_floating_mode(focused, false); + parse_cmd("exit"); + parse_cmd("exec /usr/bin/bleh"); + parse_cmd("exec kill -9 33"); + parse_cmd("kill"); + parse_cmd("[ class=\"Xpdf\" ] kill"); + parse_cmd("[ class=\"firefox\" ] kill"); + + } tree_render(); @@ -121,6 +129,7 @@ void parse_command(const char *command) { } int main(int argc, char *argv[]) { + //parse_cmd("[ foo ] attach, attach ; focus"); int screens; char *override_configpath = NULL; bool autostart = true; -- 2.39.5