1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 actions_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 "actions_parser.h"
21 #include "actions_list.h"
22 #include "actions_value.h"
29 struct _ObActionsList;
30 struct _ObActionsListTest;
31 struct _ObActionsValue;
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 _ObActionsList* parse_list(ObActionsParser *p,
41 struct _ObActionsList* parse_action(ObActionsParser *p, gboolean *e);
42 struct _ObActionsList* parse_filter(ObActionsParser *p, gboolean *e);
43 struct _ObActionsListTest* parse_filter_test(ObActionsParser *p, gboolean *e);
44 struct _ObActionsValue* parse_value(ObActionsParser *p,
45 gboolean allow_actions,
47 gchar* parse_string(ObActionsParser *p, guchar end, gboolean *e);
49 struct _ObActionsParser
55 ObActionsParser* actions_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(ObActionsParser);
89 p->scan = g_scanner_new(&config);
93 void actions_parser_ref(ObActionsParser *p)
98 void actions_parser_unref(ObActionsParser *p)
100 if (p && --p->ref < 1) {
101 g_scanner_destroy(p->scan);
102 g_slice_free(ObActionsParser, p);
106 ObActionsList* actions_parser_read_string(ObActionsParser *p,
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 ObActionsList* actions_parser_read_file(ObActionsParser *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(ObActionsParser *p, GTokenType exp, const gchar *message,
158 g_scanner_unexp_token(p->scan, exp, NULL, NULL, NULL, message, TRUE);
163 ObActionsList* parse_list(ObActionsParser *p, GTokenType end, gboolean *e)
165 ObActionsList *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) {
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 ObActionsList* parse_action(ObActionsParser *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)actions_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 ObActionsValue *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(ObActionsList);
267 al->isfilter = FALSE;
268 al->u.action = actions_act_new(name, config);
271 g_hash_table_unref(config);
275 ObActionsList* parse_filter(ObActionsParser *p, gboolean *e)
278 ObActionsList *al, *thendo, *elsedo;
279 ObActionsListTest *test;
281 /* read the filter tests */
282 test = parse_filter_test(p, e);
284 actions_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(ObActionsList);
307 al->u.f.thendo = thendo;
308 al->u.f.elsedo = elsedo;
313 ObActionsListTest* parse_filter_test(ObActionsParser *p, gboolean *e)
317 ObActionsValue *value;
319 ObActionsListTest *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 actions_value_unref(value);
349 /* check if there is another test and how we're connected */
350 t = g_scanner_get_next_token(p->scan);
351 if (t == ',') { /* and */
353 next = parse_filter_test(p, e);
355 else if (t == '|') { /* or */
357 next = parse_filter_test(p, e);
359 else if (t == ']') /* end of the filter */
362 parse_error(p, ']', NULL, e);
364 /* don't allow any errors */
367 actions_value_unref(value);
368 actions_list_test_destroy(next);
372 ObActionsListTest *test;
374 test = g_slice_new(ObActionsListTest);
383 ObActionsValue* parse_value(ObActionsParser *p,
384 gboolean allow_actions,
391 t = g_scanner_get_next_token(p->scan);
392 if (t == G_TOKEN_IDENTIFIER) {
393 v = actions_value_new_string(p->scan->value.v_string);
396 v = actions_value_new_string(parse_string(p, '"', e));
398 v = actions_value_new_string(parse_string(p, ')', e));
399 else if (t == '{' && allow_actions) {
400 ObActionsList *l = parse_list(p, '}', e);
401 if (l) v = actions_value_new_actions_list(l);
404 parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e);
409 gchar* parse_string(ObActionsParser *p, guchar end, gboolean *e)
413 const gchar *error_message = NULL;
415 /* inside a string we want everything to be parsed as text (identifiers) */
416 p->scan->config->cset_skip_characters = "";
417 p->scan->config->cset_identifier_first = IDENTIFIER_NTH " ";
418 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH " ";
420 buf = g_string_new("");
422 t = g_scanner_get_next_token(p->scan);
424 if (t == G_TOKEN_IDENTIFIER)
425 g_string_append(buf, p->scan->value.v_string);
426 else if (t == G_TOKEN_EOF) {
427 error_message = _("Missing end of quoted string");
428 goto parse_string_error;
430 else if (t == '\\') { /* escape sequence */
431 t = g_scanner_get_next_token(p->scan);
432 if (!strchr(ESCAPE_SEQS, t)) {
433 error_message = _("Unknown escape sequence");
434 goto parse_string_error;
436 g_string_append_c(buf, t);
438 else /* other single character */
439 g_string_append_c(buf, t);
441 t = g_scanner_get_next_token(p->scan);
444 /* reset to their default values */
445 p->scan->config->cset_skip_characters = SKIP;
446 p->scan->config->cset_identifier_first = IDENTIFIER_FIRST;
447 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH;
451 g_string_free(buf, FALSE);
456 g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL,
457 error_message, TRUE);
458 g_string_free(buf, TRUE);