From: Dana Jansens Date: Tue, 26 Jul 2011 22:06:42 +0000 (-0400) Subject: Add a parser for the action command language. X-Git-Url: http://git.openbox.org/?a=commitdiff_plain;h=b6afecf70ea5490b04bcb57058bfd61032d274fc;p=dana%2Fopenbox.git Add a parser for the action command language. The parser generates list of actions (complete with filters). The parser isn't called anywhere yet, and uses the incomplete actions_act_new() function. TODO: The filters now are just text string key:value pairs. But they should become intelligent objects like actions are. The actions need to change their option parsing from XML to the new key:value pair lists. --- diff --git a/Makefile.am b/Makefile.am index 3967a727..542a2474 100644 --- a/Makefile.am +++ b/Makefile.am @@ -238,6 +238,10 @@ openbox_openbox_SOURCES = \ openbox/actions/unfocus.c \ openbox/actions.c \ openbox/actions.h \ + openbox/actions_list.c \ + openbox/actions_list.h \ + openbox/actions_parser.c \ + openbox/actions_parser.h \ openbox/apps_menu.c \ openbox/apps_menu.h \ openbox/client.c \ diff --git a/openbox/actions.c b/openbox/actions.c index ee9d55f1..2288ea6d 100644 --- a/openbox/actions.c +++ b/openbox/actions.c @@ -251,6 +251,33 @@ ObActionsAct* actions_parse(xmlNodePtr node) return act; } +ObActionsAct* actions_act_new(const gchar *name, GList *keys, GList *values) +{ + ObActionsAct *act = NULL; + + act = actions_build_act_from_string(name); + if (act) { + /* there is more stuff to parse here */ + if (act->def->canbeinteractive) { + if (act->def->setup.i) +//XXX act->options = act->def->setup.i(keys, values, + act->options = act->def->setup.i(NULL, + &act->i_pre, + &act->i_input, + &act->i_cancel, + &act->i_post); + } + else { + if (act->def->setup.n) +//XXX act->options = act->def->setup.n(keys, values); + act->options = act->def->setup.n(NULL); + } + } + g_free(name); + + return act; +} + gboolean actions_act_is_interactive(ObActionsAct *act) { return act->i_input != NULL; diff --git a/openbox/actions.h b/openbox/actions.h index e03bc577..16a48ca8 100644 --- a/openbox/actions.h +++ b/openbox/actions.h @@ -88,6 +88,14 @@ ObActionsAct* actions_parse_string(const gchar *name); gboolean actions_act_is_interactive(ObActionsAct *act); +/*! Create a new ObActionAct structure. + @name The name of the action. + @keys The names of the options passed to the action. + @values The values of the options passed to the action, paired with the + keys. These are ObActionsListValue objects. +*/ +ObActionsAct* actions_act_new(const gchar *name, GList *keys, GList *values); + void actions_act_ref(ObActionsAct *act); void actions_act_unref(ObActionsAct *act); diff --git a/openbox/actions_list.c b/openbox/actions_list.c new file mode 100644 index 00000000..20f2b90e --- /dev/null +++ b/openbox/actions_list.c @@ -0,0 +1,159 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + actions_list.c for the Openbox window manager + Copyright (c) 2011 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "actions_list.h" + +#include + +struct _ObActionsListValue { + gint ref; + enum { + OB_AL_STRING, + OB_AL_INTEGER, + OB_AL_ACTIONSLIST + } type; + union { + gchar *string; + guint integer; + ObActionsList *actions; + } v; +}; + +void actions_list_ref(ObActionsList *l) +{ + ++l->ref; +} + +void actions_list_unref(ObActionsList *l) +{ + while (l && --l->ref < 1) { + ObActionsList *n = l->next; + + if (l->isfilter) { + actions_list_test_destroy(l->u.f.test); + actions_list_unref(l->u.f.thendo); + actions_list_unref(l->u.f.elsedo); + } + else { + actions_act_unref(l->u.action); + } + g_slice_free(ObActionsList, l); + l = n; + } +} + +void actions_list_test_destroy(ObActionsListTest *t) +{ + while (t) { + ObActionsListTest *n = t->next; + + g_free(t->key); + actions_list_value_unref(t->value); + g_slice_free(ObActionsListTest, t); + t = n; + } +} + +ObActionsListValue* actions_list_value_new_string(const gchar *s) +{ + return actions_list_value_new_string_steal(g_strdup(s)); +} + +ObActionsListValue* actions_list_value_new_string_steal(gchar *s) +{ + ObActionsListValue *v = g_slice_new(ObActionsListValue); + v->ref = 1; + v->type = OB_AL_STRING; + v->v.string = s; + return v; +} + +ObActionsListValue* actions_list_value_new_int(gint i) +{ + ObActionsListValue *v = g_slice_new(ObActionsListValue); + v->ref = 1; + v->type = OB_AL_INTEGER; + v->v.integer = i; + return v; +} + +ObActionsListValue* actions_list_value_new_actions_list(ObActionsList *al) +{ + ObActionsListValue *v = g_slice_new(ObActionsListValue); + v->ref = 1; + v->type = OB_AL_ACTIONSLIST; + v->v.actions = al; + actions_list_ref(al); + return v; +} + +void actions_list_value_ref(ObActionsListValue *v) +{ + ++v->ref; +} + +void actions_list_value_unref(ObActionsListValue *v) +{ + if (v && --v->ref < 1) { + switch (v->type) { + case OB_AL_STRING: + g_free(v->v.string); + break; + case OB_AL_ACTIONSLIST: + actions_list_unref(v->v.actions); + break; + case OB_AL_INTEGER: + break; + } + g_slice_free(ObActionsListValue, v); + } +} + +gboolean actions_list_value_is_string(ObActionsListValue *v) +{ + return v->type == OB_AL_STRING; +} + +gboolean actions_list_value_is_int(ObActionsListValue *v) +{ + return v->type == OB_AL_INTEGER; +} + +gboolean actions_list_value_is_actions_list(ObActionsListValue *v) +{ + return v->type == OB_AL_ACTIONSLIST; +} + +gchar* actions_list_value_string(ObActionsListValue *v) +{ + g_return_val_if_fail(v->type == OB_AL_STRING, NULL); + return v->v.string; +} + +gint actions_list_value_int(ObActionsListValue *v) +{ + g_return_val_if_fail(v->type == OB_AL_INTEGER, 0); + return v->v.integer; +} + +ObActionsList* actions_list_value_actions_list(ObActionsListValue *v) +{ + g_return_val_if_fail(v->type == OB_AL_ACTIONSLIST, NULL); + return v->v.actions; +} + diff --git a/openbox/actions_list.h b/openbox/actions_list.h new file mode 100644 index 00000000..f5710e36 --- /dev/null +++ b/openbox/actions_list.h @@ -0,0 +1,74 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + actions_list.h for the Openbox window manager + Copyright (c) 2011 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "actions.h" + +#include + +typedef struct _ObActionsList ObActionsList; +typedef struct _ObActionsListTest ObActionsListTest; +typedef struct _ObActionsListValue ObActionsListValue; + +/*! Each node of the Actions list is an action itself (or a filter bound to + an action). */ +struct _ObActionsList { + gint ref; + gboolean isfilter; + union { + struct _ObActionsListFilter { + ObActionsListTest *test; /* can be null */ + ObActionsList *thendo; /* can be null */ + ObActionsList *elsedo; /* can be null */ + } f; + ObActionsAct *action; + } u; + ObActionsList *next; +}; + +struct _ObActionsListTest { + gchar *key; + ObActionsListValue *value; /* can be null */ + gboolean and; + ObActionsListTest *next; +}; + +void actions_list_ref(ObActionsList *l); +void actions_list_unref(ObActionsList *l); + +void actions_list_test_destroy(ObActionsListTest *t); + +/*! Creates a new value by making a copy of the given string. */ +ObActionsListValue* actions_list_value_new_string(const gchar *s); +/*! Creates a new value from a string, and steals ownership of the string. It + will be freed when then value is destroyed. */ +ObActionsListValue* actions_list_value_new_string_steal(gchar *s); +/*! Creates a new value that holds an integer. */ +ObActionsListValue* actions_list_value_new_int(gint i); +/*! Creates a new value with a given actions list. */ +ObActionsListValue* actions_list_value_new_actions_list(ObActionsList *al); + +void actions_list_value_ref(ObActionsListValue *v); +void actions_list_value_unref(ObActionsListValue *v); + +gboolean actions_list_value_is_string(ObActionsListValue *v); +gboolean actions_list_value_is_int(ObActionsListValue *v); +gboolean actions_list_value_is_actions_list(ObActionsListValue *v); + +gchar* actions_list_value_string(ObActionsListValue *v); +gint actions_list_value_int(ObActionsListValue *v); +ObActionsList* actions_list_value_actions_list(ObActionsListValue *v); diff --git a/openbox/actions_parser.c b/openbox/actions_parser.c new file mode 100644 index 00000000..2ef8ee8f --- /dev/null +++ b/openbox/actions_parser.c @@ -0,0 +1,436 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + actions_parser.c for the Openbox window manager + Copyright (c) 2011 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "actions_parser.h" +#include "gettext.h" + +#ifdef HAVE_STRING_H +# include +#endif + +#define SKIP " \t" +#define IDENTIFIER_FIRST G_CSET_a_2_z G_CSET_A_2_Z +#define IDENTIFIER_NTH G_CSET_a_2_z G_CSET_A_2_Z \ + G_CSET_DIGITS G_CSET_LATINS G_CSET_LATINC "_-" +#define ESCAPE_SEQS "\"()" + +ObActionsList* parse_list(ObActionsParser *p, GTokenType end, gboolean *e); +ObActionsList* parse_action(ObActionsParser *p, gboolean *e); +ObActionsList* parse_filter(ObActionsParser *p, gboolean *e); +ObActionsListTest* parse_filter_test(ObActionsParser *p, gboolean *e); +ObActionsListValue* parse_value(ObActionsParser *p, + gboolean allow_actions, + gboolean *e); +gchar* parse_string(ObActionsParser *p, guchar end, gboolean *e); + +struct _ObActionsParser +{ + gint ref; + GScanner *scan; +}; + +ObActionsParser* actions_parser_new(void) +{ + ObActionsParser *p; + GScannerConfig config = { + .cset_skip_characters = SKIP, + .cset_identifier_first = IDENTIFIER_FIRST, + .cset_identifier_nth = IDENTIFIER_NTH, + .cpair_comment_single = "#\n", + .case_sensitive = FALSE, + .skip_comment_multi = TRUE, + .skip_comment_single = TRUE, + .scan_comment_multi = FALSE, + .scan_identifier = TRUE, + .scan_identifier_1char = TRUE, + .scan_identifier_NULL = FALSE, + .scan_symbols = TRUE, + .scan_binary = FALSE, + .scan_octal = FALSE, + .scan_float = TRUE, + .scan_hex = FALSE, + .scan_hex_dollar = FALSE, + .scan_string_sq = FALSE, + .scan_string_dq = FALSE, + .numbers_2_int = TRUE, + .int_2_float = FALSE, + .identifier_2_string = FALSE, + .char_2_token = TRUE, + .symbol_2_token = FALSE, + .scope_0_fallback = FALSE, + .store_int64 = FALSE + }; + + p = g_slice_new(ObActionsParser); + p->ref = 1; + p->scan = g_scanner_new(&config); + return p; +} + +void actions_parser_ref(ObActionsParser *p) +{ + ++p->ref; +} + +void actions_parser_unref(ObActionsParser *p) +{ + if (p && --p->ref < 1) { + g_scanner_destroy(p->scan); + g_slice_free(ObActionsParser, p); + } +} + +ObActionsList* actions_parser_read_string(ObActionsParser *p, + const gchar *text) +{ + gboolean e; + + g_scanner_input_text(p->scan, text, strlen(text)); + p->scan->input_name = "(console)"; + + return parse_list(p, G_TOKEN_EOF, &e); +} + +ObActionsList* actions_parser_read_file(ObActionsParser *p, + const gchar *file, + GError **error) +{ + GIOChannel *ch; + gboolean e; + + ch = g_io_channel_new_file(file, "r", error); + if (!ch) return NULL; + + g_scanner_input_file(p->scan, g_io_channel_unix_get_fd(ch)); + p->scan->input_name = file; + + return parse_list(p, G_TOKEN_EOF, &e); +} + +/************* Parser functions. The list is the entry point. ************ +BNF for the language: + +TEST := KEY=VALUE | KEY +ACTION := [FILTER] ACTION ACTIONELSE | ACTIONNAME ACTIONOPTS | {ACTIONLIST} +ACTIONLIST := ACTION ACTIONLIST | ACTION +ACTIONELSE := nil | \| ACTION +FILTER := FILTERORS +FILTERORS := FILTERANDS \| FILTERORS | FILTERANDS +FILTERANDS := TEST, FILTERANDS | TEST +ACTIONOPTS := ACTIONOPT ACTIONOPTS | ACTIONOPT +ACTIONOPT := ATTRIBUTE:WORD | ATTRIBUTE:STRING | ATTRIBUTE:{ACTIONLIST} +WORD := string of text without any spaces +STRING := "TEXT" | (TEXT) | + where TEXT is a string, any occurance of the closing quote character must + be escaped by an backslash. + \\ \( \) and \" are all valid escaped characters. +************** ************/ + +gpointer parse_error(ObActionsParser *p, GTokenType exp, const gchar *message, + gboolean *e) +{ + g_scanner_unexp_token(p->scan, exp, NULL, NULL, NULL, message, TRUE); + *e = TRUE; + return NULL; +} + +ObActionsList* parse_list(ObActionsParser *p, GTokenType end, gboolean *e) +{ + ObActionsList *first, *last; + GTokenType t; + + first = last = NULL; + + t = g_scanner_peek_next_token(p->scan); + while (t != end && t != G_TOKEN_EOF) { + if (t == '\n') + g_scanner_get_next_token(p->scan); /* skip empty lines */ + else { + ObActionsList *next; + + /* parse the next action and stick it on the end of the list */ + next = parse_action(p, e); + if (last) last->next = next; + if (!first) first = next; + last = next; + + if (*e) break; /* don't parse any more after an error */ + } + + t = g_scanner_peek_next_token(p->scan); + } + + /* eat the ending character */ + g_scanner_get_next_token(p->scan); + + return first; +} + +ObActionsList* parse_action(ObActionsParser *p, gboolean *e) +{ + GTokenType t; + ObActionsList *al; + gchar *name; + GList *keys, *values; + + t = g_scanner_get_next_token(p->scan); + + if (t == '[') return parse_filter(p, e); + if (t == '{') return parse_list(p, '}', e); + + /* check for a name */ + if (t != G_TOKEN_IDENTIFIER) + return parse_error(p, G_TOKEN_NONE, _("Expected an action name"), e); + + /* read the action's name */ + name = g_strdup(p->scan->value.v_string); + keys = values = NULL; + + /* read the action's options key:value pairs */ + t = g_scanner_peek_next_token(p->scan); + while (t == G_TOKEN_IDENTIFIER) { + gchar *key; + ObActionsListValue *value; + + g_scanner_get_next_token(p->scan); /* eat the key */ + t = g_scanner_peek_next_token(p->scan); /* check for ':' */ + if (t != ':') { + parse_error(p, ':', NULL, e); + break; /* don't read any more options */ + } + + /* save the key */ + key = g_strdup(p->scan->value.v_string); + g_scanner_get_next_token(p->scan); /* eat the ':' */ + + /* read the value */ + value = parse_value(p, TRUE, e); + + /* check if we read a value (regardless of errors), and save + the key:value pair if we did. */ + if (value) { + keys = g_list_prepend(keys, key); + values = g_list_prepend(values, value); + } + else + g_free(key); /* didn't read any value */ + + if (*e) break; /* don't parse any more if there was an error */ + } + + al = g_slice_new(ObActionsList); + al->ref = 1; + al->isfilter = FALSE; + al->u.action = actions_act_new(name, keys, values); + return al; +} + +ObActionsList* parse_filter(ObActionsParser *p, gboolean *e) +{ + GTokenType t; + ObActionsList *al, *thendo, *elsedo; + ObActionsListTest *test; + + /* read the filter tests */ + test = parse_filter_test(p, e); + if (*e) { + actions_list_test_destroy(test); + return NULL; + } + + /* read the action for the filter */ + thendo = parse_action(p, e); + elsedo = NULL; + + if (!*e) { + /* check for else case */ + t = g_scanner_peek_next_token(p->scan); + if (t == '|') { + g_scanner_get_next_token(p->scan); /* eat it */ + + /* read the else action for the filter */ + elsedo = parse_action(p, e); + } + } + + al = g_slice_new(ObActionsList); + al->ref = 1; + al->isfilter = TRUE; + al->u.f.test = test; + al->u.f.thendo = thendo; + al->u.f.elsedo = elsedo; + return al; +} + +ObActionsListTest* parse_filter_test(ObActionsParser *p, gboolean *e) +{ + GTokenType t; + gchar *key; + ObActionsListValue *value; + gboolean and; + ObActionsListTest *next; + + t = g_scanner_get_next_token(p->scan); + if (t == ']') /* empty */ + return NULL; + + else if (t != G_TOKEN_IDENTIFIER) + return parse_error(p, G_TOKEN_NONE, + _("Expected a filter test lvalue"), e); + + /* got a key */ + key = g_strdup(p->scan->value.v_string); + value = NULL; + and = FALSE; + next = NULL; + + /* check if it has a value also */ + t = g_scanner_peek_next_token(p->scan); + if (t == '=') { + g_scanner_get_next_token(p->scan); /* eat the = */ + value = parse_value(p, FALSE, e); + } + + /* don't allow any errors (but value can be null if not present) */ + if (*e) { + g_free(key); + actions_list_value_unref(value); + return NULL; + } + + /* check if there is another test and how we're connected */ + t = g_scanner_get_next_token(p->scan); + switch (t) { + case ',': /* and */ + and = TRUE; + next = parse_filter_test(p, e); + break; + case '|': /* or */ + and = FALSE; + next = parse_filter_test(p, e); + break; + case ']': /* end of the filter */ + break; + default: + parse_error(p, ']', NULL, e); + } + + /* don't allow any errors */ + if (*e) { + g_free(key); + actions_list_value_unref(value); + actions_list_test_destroy(next); + return NULL; + } + else { + ObActionsListTest *test; + + test = g_slice_new(ObActionsListTest); + test->key = key; + test->value = value; + test->and = and; + test->next = next; + return test; + } +} + +ObActionsListValue* parse_value(ObActionsParser *p, + gboolean allow_actions, + gboolean *e) +{ + GTokenType t; + ObActionsListValue *v; + + v = NULL; + t = g_scanner_get_next_token(p->scan); + if (t == G_TOKEN_IDENTIFIER) + v = actions_list_value_new_string(p->scan->value.v_string); + else if (t == G_TOKEN_INT) + v = actions_list_value_new_int(p->scan->value.v_int); + else if (t == '"') + v = actions_list_value_new_string(parse_string(p, '"', e)); + else if (t == '(') + v = actions_list_value_new_string(parse_string(p, ')', e)); + else if (t == '{' && allow_actions) { + ObActionsList *l = parse_list(p, '}', e); + if (l) v = actions_list_value_new_actions_list(l); + } + else + parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e); + return v; +} + + +gchar* parse_string(ObActionsParser *p, guchar end, gboolean *e) +{ + GString *buf; + GTokenType t; + const gchar *error_message = NULL; + + /* inside a string we want everything to be parsed as text (identifiers) */ + p->scan->config->cset_skip_characters = ""; + p->scan->config->cset_identifier_first = IDENTIFIER_NTH " "; + p->scan->config->cset_identifier_nth = IDENTIFIER_NTH " "; + + buf = g_string_new(""); + + t = g_scanner_get_next_token(p->scan); + while (t != end) { + switch (t) { + case G_TOKEN_IDENTIFIER: + g_string_append(buf, p->scan->value.v_string); + break; + case G_TOKEN_EOF: + error_message = _("Missing end of quoted string"); + goto parse_string_error; + case G_TOKEN_NONE: + error_message = _("Unknown token in quoted string"); + goto parse_string_error; + case '\\': /* escape sequence */ + t = g_scanner_get_next_token(p->scan); + if (!strchr(ESCAPE_SEQS, t)) { + error_message = _("Unknown escape sequence"); + goto parse_string_error; + } + g_string_append_c(buf, t); + break; + default: /* other single character */ + g_string_append_c(buf, t); + } + + t = g_scanner_get_next_token(p->scan); + } + + /* reset to their default values */ + p->scan->config->cset_skip_characters = SKIP; + p->scan->config->cset_identifier_first = IDENTIFIER_FIRST; + p->scan->config->cset_identifier_nth = IDENTIFIER_NTH; + + { + gchar *v = buf->str; + g_string_free(buf, FALSE); + return v; + } + +parse_string_error: + g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL, + error_message, TRUE); + g_string_free(buf, TRUE); + *e = TRUE; + return NULL; +} diff --git a/openbox/actions_parser.h b/openbox/actions_parser.h new file mode 100644 index 00000000..5d26a293 --- /dev/null +++ b/openbox/actions_parser.h @@ -0,0 +1,34 @@ +/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*- + + actions_parser.h for the Openbox window manager + Copyright (c) 2011 Dana Jansens + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + See the COPYING file for a copy of the GNU General Public License. +*/ + +#include "actions_list.h" + +#include + +typedef struct _ObActionsParser ObActionsParser; + +ObActionsParser* actions_parser_new(void); + +void actions_parser_ref(ObActionsParser *p); +void actions_parser_unref(ObActionsParser *p); + +ObActionsList* actions_parser_read_string(ObActionsParser *p, + const gchar *text); +ObActionsList* actions_parser_read_file(ObActionsParser *p, + const gchar *file, + GError **error);