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 "action_value.h"
31 struct _ObActionListTest;
32 struct _ObActionValue;
35 #define IDENTIFIER_FIRST G_CSET_a_2_z G_CSET_A_2_Z G_CSET_DIGITS "-_"
36 #define IDENTIFIER_NTH IDENTIFIER_FIRST G_CSET_LATINS G_CSET_LATINC
37 #define ESCAPE_SEQS "\"()"
39 struct _ObActionList* parse_list(ObActionParser *p,
42 struct _ObActionList* parse_action(ObActionParser *p, gboolean *e);
43 struct _ObActionList* parse_filter(ObActionParser *p, gboolean *e);
44 struct _ObActionListTest* parse_filter_test(ObActionParser *p, gboolean *e);
45 struct _ObActionValue* parse_value(ObActionParser *p,
46 gboolean allow_actions,
48 gchar* parse_string(ObActionParser *p, guchar end, gboolean *e);
50 struct _ObActionParser
56 ObActionParser* action_parser_new(void)
59 GScannerConfig config = {
60 .cset_skip_characters = SKIP,
61 .cset_identifier_first = IDENTIFIER_FIRST,
62 .cset_identifier_nth = IDENTIFIER_NTH,
63 .cpair_comment_single = "#\n",
64 .case_sensitive = FALSE,
65 .skip_comment_multi = TRUE,
66 .skip_comment_single = TRUE,
67 .scan_comment_multi = FALSE,
68 .scan_identifier = TRUE,
69 .scan_identifier_1char = TRUE,
70 .scan_identifier_NULL = FALSE,
76 .scan_hex_dollar = FALSE,
77 .scan_string_sq = FALSE,
78 .scan_string_dq = FALSE,
79 .numbers_2_int = TRUE,
81 .identifier_2_string = FALSE,
83 .symbol_2_token = FALSE,
84 .scope_0_fallback = FALSE,
88 p = g_slice_new(ObActionParser);
90 p->scan = g_scanner_new(&config);
94 void action_parser_ref(ObActionParser *p)
99 void action_parser_unref(ObActionParser *p)
101 if (p && --p->ref < 1) {
102 g_scanner_destroy(p->scan);
103 g_slice_free(ObActionParser, p);
107 ObActionList* action_parser_read_string(ObActionParser *p, const gchar *text)
111 g_scanner_input_text(p->scan, text, strlen(text));
112 p->scan->input_name = "(console)";
115 return parse_list(p, G_TOKEN_EOF, &e);
118 ObActionList* actions_parser_read_file(ObActionParser *p,
125 ch = g_io_channel_new_file(file, "r", error);
126 if (!ch) return NULL;
128 g_scanner_input_file(p->scan, g_io_channel_unix_get_fd(ch));
129 p->scan->input_name = file;
132 return parse_list(p, G_TOKEN_EOF, &e);
135 /************* Parser functions. The list is the entry point. ************
136 BNF for the language:
138 TEST := KEY=VALUE | KEY
139 ACTION := [FILTER] ACTION ELSE END | ACTIONNAME ACTIONOPTS | {ACTIONLIST}
140 ELSE := nil | \| ACTION
142 ACTIONLIST := ACTION ACTIONLIST | ACTION
144 FILTERORS := FILTERANDS \| FILTERORS | FILTERANDS
145 FILTERANDS := TEST, FILTERANDS | TEST
146 ACTIONOPTS := ACTIONOPT ACTIONOPTS | ACTIONOPT
147 ACTIONOPT := ATTRIBUTE:WORD | ATTRIBUTE:STRING | ATTRIBUTE:{ACTIONLIST}
148 WORD := string of text without any spaces
149 STRING := "TEXT" | (TEXT) |
150 where TEXT is a string, any occurance of the closing quote character must
151 be escaped by an backslash.
152 \\ \( \) and \" are all valid escaped characters.
153 ************** ************/
155 gpointer parse_error(ObActionParser *p, GTokenType exp, const gchar *message,
158 g_scanner_unexp_token(p->scan, exp, NULL, NULL, NULL, message, TRUE);
163 ObActionList* parse_list(ObActionParser *p, GTokenType end, gboolean *e)
165 ObActionList *first, *last;
170 t = g_scanner_peek_next_token(p->scan);
171 while (t != end && t != G_TOKEN_EOF) {
173 g_scanner_get_next_token(p->scan); /* skip empty lines */
175 g_scanner_get_next_token(p->scan); /* separator */
177 else if (t == G_TOKEN_IDENTIFIER || t == '[' || t == '{') {
180 /* parse the next action and stick it on the end of the list */
181 next = parse_action(p, e);
182 if (last) last->next = next;
183 if (!first) first = next;
187 g_scanner_get_next_token(p->scan);
188 parse_error(p, (end ? end : G_TOKEN_NONE),
189 _("Expected an action or end of action list"), e);
192 if (*e) break; /* don't parse any more after an error */
194 t = g_scanner_peek_next_token(p->scan);
197 /* eat the ending character */
198 g_scanner_get_next_token(p->scan);
203 ObActionList* parse_action(ObActionParser *p, gboolean *e)
210 t = g_scanner_get_next_token(p->scan);
212 if (t == '[') return parse_filter(p, e);
213 if (t == '{') return parse_list(p, '}', e);
215 /* check for a name */
216 if (t != G_TOKEN_IDENTIFIER)
217 return parse_error(p, G_TOKEN_NONE, _("Expected an action name"), e);
219 /* read the action's name */
220 name = g_strdup(p->scan->value.v_string);
221 config = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
222 (GDestroyNotify)action_value_unref);
224 /* read the action's options key:value pairs */
225 t = g_scanner_peek_next_token(p->scan);
226 while (t == G_TOKEN_IDENTIFIER || t == '\\') {
228 g_scanner_get_next_token(p->scan); /* eat it */
229 t = g_scanner_get_next_token(p->scan); /* check for '\n' */
231 parse_error(p, G_TOKEN_NONE, _("Expected newline"), e);
235 ObActionValue *value;
237 g_scanner_get_next_token(p->scan); /* eat the key */
238 t = g_scanner_peek_next_token(p->scan); /* check for ':' */
240 g_scanner_get_next_token(p->scan);
241 parse_error(p, ':', NULL, e);
242 break; /* don't read any more options */
246 key = g_strdup(p->scan->value.v_string);
247 g_scanner_get_next_token(p->scan); /* eat the ':' */
250 value = parse_value(p, TRUE, e);
252 /* check if we read a value (regardless of errors), and save
253 the key:value pair if we did. */
255 g_hash_table_replace(config, key, value);
257 g_free(key); /* didn't read any value */
260 if (*e) break; /* don't parse any more if there was an error */
262 t = g_scanner_peek_next_token(p->scan);
265 al = g_slice_new(ObActionList);
267 al->isfilterset = FALSE;
268 al->u.action = action_new(name, config);
271 g_hash_table_unref(config);
275 ObActionList* parse_filter(ObActionParser *p, gboolean *e)
278 ObActionList *al, *thendo, *elsedo;
279 ObActionListTest *test;
281 /* read the filter tests */
282 test = parse_filter_test(p, e);
284 action_list_test_destroy(test);
288 /* read the action for the filter */
289 thendo = parse_action(p, e);
293 /* check for else case */
294 t = g_scanner_peek_next_token(p->scan);
296 g_scanner_get_next_token(p->scan); /* eat it */
298 /* read the else action for the filter */
299 elsedo = parse_action(p, e);
303 al = g_slice_new(ObActionList);
305 al->isfilterset = TRUE;
307 al->u.f.thendo = thendo;
308 al->u.f.elsedo = elsedo;
313 ObActionListTest* parse_filter_test(ObActionParser *p, gboolean *e)
317 ObActionValue *value;
319 ObActionFilter *filter;
320 ObActionListTest *next;
322 t = g_scanner_get_next_token(p->scan);
323 if (t == ']') /* empty */
326 else if (t != G_TOKEN_IDENTIFIER)
327 return parse_error(p, G_TOKEN_NONE,
328 _("Expected a filter test lvalue"), e);
331 key = g_strdup(p->scan->value.v_string);
336 /* check if it has a value also */
337 t = g_scanner_peek_next_token(p->scan);
339 g_scanner_get_next_token(p->scan); /* eat the = */
340 value = parse_value(p, FALSE, e);
343 /* don't allow any errors (but value can be null if not present) */
346 action_value_unref(value);
350 filter = action_filter_new(key, value);
353 m = g_strdup_printf(_("Unable to create filter: %s"), key);
354 parse_error(p, G_TOKEN_NONE, m, e);
359 action_value_unref(value);
363 /* check if there is another test and how we're connected */
364 t = g_scanner_get_next_token(p->scan);
365 if (t == ',') { /* and */
367 next = parse_filter_test(p, e);
369 else if (t == '|') { /* or */
371 next = parse_filter_test(p, e);
373 else if (t == ']') /* end of the filter */
376 parse_error(p, ']', NULL, e);
378 /* don't allow any errors */
380 action_filter_unref(filter);
381 action_list_test_destroy(next);
385 ObActionListTest *test;
387 test = g_slice_new(ObActionListTest);
388 test->filter = filter;
395 ObActionValue* parse_value(ObActionParser *p,
396 gboolean allow_actions,
403 t = g_scanner_get_next_token(p->scan);
404 if (t == G_TOKEN_IDENTIFIER) {
405 v = action_value_new_string(p->scan->value.v_string);
408 v = action_value_new_string(parse_string(p, '"', e));
410 v = action_value_new_string(parse_string(p, ')', e));
411 else if (t == '{' && allow_actions) {
412 ObActionList *l = parse_list(p, '}', e);
413 if (l) v = action_value_new_action_list(l);
416 parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e);
421 gchar* parse_string(ObActionParser *p, guchar end, gboolean *e)
425 const gchar *error_message = NULL;
427 /* inside a string we want everything to be parsed as text (identifiers) */
428 p->scan->config->cset_skip_characters = "";
429 p->scan->config->cset_identifier_first = IDENTIFIER_NTH " ";
430 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH " ";
432 buf = g_string_new("");
434 t = g_scanner_get_next_token(p->scan);
436 if (t == G_TOKEN_IDENTIFIER)
437 g_string_append(buf, p->scan->value.v_string);
438 else if (t == G_TOKEN_EOF) {
439 error_message = _("Missing end of quoted string");
440 goto parse_string_error;
442 else if (t == '\\') { /* escape sequence */
443 t = g_scanner_get_next_token(p->scan);
444 if (!strchr(ESCAPE_SEQS, t)) {
445 error_message = _("Unknown escape sequence");
446 goto parse_string_error;
448 g_string_append_c(buf, t);
450 else /* other single character */
451 g_string_append_c(buf, t);
453 t = g_scanner_get_next_token(p->scan);
456 /* reset to their default values */
457 p->scan->config->cset_skip_characters = SKIP;
458 p->scan->config->cset_identifier_first = IDENTIFIER_FIRST;
459 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH;
463 g_string_free(buf, FALSE);
468 g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL,
469 error_message, TRUE);
470 g_string_free(buf, TRUE);