1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 action_parser.c for the Openbox window manager
4 Copyright (c) 2011 Dana Jansens
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 See the COPYING file for a copy of the GNU General Public License.
19 #include "action_parser.h"
21 #include "action_filter.h"
22 #include "action_list.h"
23 #include "config_value.h"
31 struct _ObActionListTest;
34 #define IDENTIFIER_FIRST G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "-_"
35 #define IDENTIFIER_NTH IDENTIFIER_FIRST G_CSET_LATINS G_CSET_LATINC
36 #define ESCAPE_SEQS "\"()"
38 struct _ObActionList* parse_list(ObActionParser *p,
41 struct _ObActionList* parse_action(ObActionParser *p, gboolean *e);
42 struct _ObActionList* parse_filter(ObActionParser *p, gboolean *e);
43 struct _ObActionListTest* parse_filter_test(ObActionParser *p, gboolean *e);
44 struct _ObConfigValue* parse_value(ObActionParser *p,
45 gboolean allow_actions,
47 gchar* parse_string(ObActionParser *p, guchar end, gboolean *e);
49 struct _ObActionParser
55 ObActionParser* action_parser_new(void)
58 GScannerConfig config = {
59 .cset_skip_characters = SKIP,
60 .cset_identifier_first = IDENTIFIER_FIRST,
61 .cset_identifier_nth = IDENTIFIER_NTH,
62 .cpair_comment_single = "#\n",
63 .case_sensitive = FALSE,
64 .skip_comment_multi = TRUE,
65 .skip_comment_single = TRUE,
66 .scan_comment_multi = FALSE,
67 .scan_identifier = TRUE,
68 .scan_identifier_1char = TRUE,
69 .scan_identifier_NULL = FALSE,
75 .scan_hex_dollar = FALSE,
76 .scan_string_sq = FALSE,
77 .scan_string_dq = FALSE,
78 .numbers_2_int = TRUE,
80 .identifier_2_string = FALSE,
82 .symbol_2_token = FALSE,
83 .scope_0_fallback = FALSE,
87 p = g_slice_new(ObActionParser);
89 p->scan = g_scanner_new(&config);
93 void action_parser_ref(ObActionParser *p)
98 void action_parser_unref(ObActionParser *p)
100 if (p && --p->ref < 1) {
101 g_scanner_destroy(p->scan);
102 g_slice_free(ObActionParser, p);
106 ObActionList* action_parser_read_string(ObActionParser *p, const gchar *text)
110 g_scanner_input_text(p->scan, text, strlen(text));
111 p->scan->input_name = "(console)";
114 return parse_list(p, G_TOKEN_EOF, &e);
117 ObActionList* actions_parser_read_file(ObActionParser *p,
124 ch = g_io_channel_new_file(file, "r", error);
125 if (!ch) return NULL;
127 g_scanner_input_file(p->scan, g_io_channel_unix_get_fd(ch));
128 p->scan->input_name = file;
131 return parse_list(p, G_TOKEN_EOF, &e);
134 /************* Parser functions. The list is the entry point. ************
135 BNF for the language:
137 TEST := KEY=VALUE | KEY
138 ACTION := [FILTER] ACTION ELSE END | ACTIONNAME ACTIONOPTS | {ACTIONLIST}
139 ELSE := nil | \| ACTION
141 ACTIONLIST := ACTION ACTIONLIST | ACTION
143 FILTERORS := FILTERANDS \| FILTERORS | FILTERANDS
144 FILTERANDS := TEST, FILTERANDS | TEST
145 ACTIONOPTS := ACTIONOPT ACTIONOPTS | ACTIONOPT
146 ACTIONOPT := ATTRIBUTE:WORD | ATTRIBUTE:STRING | ATTRIBUTE:{ACTIONLIST}
147 WORD := string of text without any spaces
148 STRING := "TEXT" | (TEXT) |
149 where TEXT is a string, any occurance of the closing quote character must
150 be escaped by an backslash.
151 \\ \( \) and \" are all valid escaped characters.
152 ************** ************/
154 gpointer parse_error(ObActionParser *p, GTokenType exp, const gchar *message,
157 g_scanner_unexp_token(p->scan, exp, NULL, NULL, NULL, message, TRUE);
162 ObActionList* parse_list(ObActionParser *p, GTokenType end, gboolean *e)
164 ObActionList *first, *last;
169 t = g_scanner_peek_next_token(p->scan);
170 while (t != end && t != G_TOKEN_EOF) {
172 g_scanner_get_next_token(p->scan); /* skip empty lines */
174 g_scanner_get_next_token(p->scan); /* separator */
176 else if (t == G_TOKEN_IDENTIFIER || t == '[' || t == '{') {
179 /* parse the next action and stick it on the end of the list */
180 next = parse_action(p, e);
181 if (last) last->next = next;
182 if (!first) first = next;
186 g_scanner_get_next_token(p->scan);
187 parse_error(p, (end ? end : G_TOKEN_NONE),
188 _("Expected an action or end of action list"), e);
191 if (*e) break; /* don't parse any more after an error */
193 t = g_scanner_peek_next_token(p->scan);
196 /* eat the ending character */
197 g_scanner_get_next_token(p->scan);
202 ObActionList* parse_action(ObActionParser *p, gboolean *e)
209 t = g_scanner_get_next_token(p->scan);
211 if (t == '[') return parse_filter(p, e);
212 if (t == '{') return parse_list(p, '}', e);
214 /* check for a name */
215 if (t != G_TOKEN_IDENTIFIER)
216 return parse_error(p, G_TOKEN_NONE, _("Expected an action name"), e);
218 /* read the action's name */
219 name = g_strdup(p->scan->value.v_string);
220 config = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
221 (GDestroyNotify)config_value_unref);
223 /* read the action's options key:value pairs */
224 t = g_scanner_peek_next_token(p->scan);
225 while (t == G_TOKEN_IDENTIFIER || t == '\\') {
227 g_scanner_get_next_token(p->scan); /* eat it */
228 t = g_scanner_get_next_token(p->scan); /* check for '\n' */
230 parse_error(p, G_TOKEN_NONE, _("Expected newline"), e);
234 ObConfigValue *value;
236 g_scanner_get_next_token(p->scan); /* eat the key */
237 t = g_scanner_peek_next_token(p->scan); /* check for ':' */
239 g_scanner_get_next_token(p->scan);
240 parse_error(p, ':', NULL, e);
241 break; /* don't read any more options */
245 key = g_strdup(p->scan->value.v_string);
246 g_scanner_get_next_token(p->scan); /* eat the ':' */
249 value = parse_value(p, TRUE, e);
251 /* check if we read a value (regardless of errors), and save
252 the key:value pair if we did. */
254 g_hash_table_replace(config, key, value);
256 g_free(key); /* didn't read any value */
259 if (*e) break; /* don't parse any more if there was an error */
261 t = g_scanner_peek_next_token(p->scan);
264 al = g_slice_new(ObActionList);
266 al->isfilterset = FALSE;
267 al->u.action = action_new(name, config);
270 g_hash_table_unref(config);
274 ObActionList* parse_filter(ObActionParser *p, gboolean *e)
277 ObActionList *al, *thendo, *elsedo;
278 ObActionListTest *test;
280 /* read the filter tests */
281 test = parse_filter_test(p, e);
283 action_list_test_destroy(test);
287 /* read the action for the filter */
288 thendo = parse_action(p, e);
292 /* check for else case */
293 t = g_scanner_peek_next_token(p->scan);
295 g_scanner_get_next_token(p->scan); /* eat it */
297 /* read the else action for the filter */
298 elsedo = parse_action(p, e);
302 al = g_slice_new(ObActionList);
304 al->isfilterset = TRUE;
306 al->u.f.thendo = thendo;
307 al->u.f.elsedo = elsedo;
312 ObActionListTest* parse_filter_test(ObActionParser *p, gboolean *e)
316 ObConfigValue *value;
318 ObActionFilter *filter;
319 ObActionListTest *next;
321 t = g_scanner_get_next_token(p->scan);
322 if (t == ']') /* empty */
325 else if (t != G_TOKEN_IDENTIFIER)
326 return parse_error(p, G_TOKEN_NONE,
327 _("Expected a filter test lvalue"), e);
330 key = g_strdup(p->scan->value.v_string);
335 /* check if it has a value also */
336 t = g_scanner_peek_next_token(p->scan);
338 g_scanner_get_next_token(p->scan); /* eat the = */
339 value = parse_value(p, FALSE, e);
342 /* don't allow any errors (but value can be null if not present) */
345 config_value_unref(value);
349 filter = action_filter_new(key, value);
352 m = g_strdup_printf(_("Unable to create filter: %s"), key);
353 parse_error(p, G_TOKEN_NONE, m, e);
358 config_value_unref(value);
362 /* check if there is another test and how we're connected */
363 t = g_scanner_get_next_token(p->scan);
364 if (t == ',') { /* and */
366 next = parse_filter_test(p, e);
368 else if (t == '|') { /* or */
370 next = parse_filter_test(p, e);
372 else if (t == ']') /* end of the filter */
375 parse_error(p, ']', NULL, e);
377 /* don't allow any errors */
379 action_filter_unref(filter);
380 action_list_test_destroy(next);
384 ObActionListTest *test;
386 test = g_slice_new(ObActionListTest);
387 test->filter = filter;
394 ObConfigValue* parse_value(ObActionParser *p,
395 gboolean allow_actions,
402 t = g_scanner_get_next_token(p->scan);
403 if (t == G_TOKEN_IDENTIFIER) {
404 v = config_value_new_string(p->scan->value.v_string);
407 v = config_value_new_string(parse_string(p, '"', e));
409 v = config_value_new_string(parse_string(p, ')', e));
410 else if (t == '{' && allow_actions) {
411 ObActionList *l = parse_list(p, '}', e);
412 if (l) v = config_value_new_action_list(l);
415 parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e);
420 gchar* parse_string(ObActionParser *p, guchar end, gboolean *e)
424 const gchar *error_message = NULL;
426 /* inside a string we want everything to be parsed as text (identifiers) */
427 p->scan->config->cset_skip_characters = "";
428 p->scan->config->cset_identifier_first = IDENTIFIER_NTH " ";
429 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH " ";
431 buf = g_string_new("");
433 t = g_scanner_get_next_token(p->scan);
435 if (t == G_TOKEN_IDENTIFIER)
436 g_string_append(buf, p->scan->value.v_string);
437 else if (t == G_TOKEN_EOF) {
438 error_message = _("Missing end of quoted string");
439 goto parse_string_error;
441 else if (t == '\\') { /* escape sequence */
442 t = g_scanner_get_next_token(p->scan);
443 if (!strchr(ESCAPE_SEQS, t)) {
444 error_message = _("Unknown escape sequence");
445 goto parse_string_error;
447 g_string_append_c(buf, t);
449 else /* other single character */
450 g_string_append_c(buf, t);
452 t = g_scanner_get_next_token(p->scan);
455 /* reset to their default values */
456 p->scan->config->cset_skip_characters = SKIP;
457 p->scan->config->cset_identifier_first = IDENTIFIER_FIRST;
458 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH;
462 g_string_free(buf, FALSE);
467 g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL,
468 error_message, TRUE);
469 g_string_free(buf, TRUE);