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);
48 static void display_error(GScanner *scanner, gchar *message, gboolean error);
50 struct _ObActionParser
55 ObActionParserErrorFunc on_error;
56 gpointer on_error_user_data;
59 static ObActionParserError last_error;
61 ObActionParser* action_parser_new(const gchar *source)
64 GScannerConfig config = {
65 .cset_skip_characters = SKIP,
66 .cset_identifier_first = IDENTIFIER_FIRST,
67 .cset_identifier_nth = IDENTIFIER_NTH,
68 .cpair_comment_single = "#\n",
69 .case_sensitive = FALSE,
70 .skip_comment_multi = TRUE,
71 .skip_comment_single = TRUE,
72 .scan_comment_multi = FALSE,
73 .scan_identifier = TRUE,
74 .scan_identifier_1char = TRUE,
75 .scan_identifier_NULL = FALSE,
81 .scan_hex_dollar = FALSE,
82 .scan_string_sq = FALSE,
83 .scan_string_dq = FALSE,
84 .numbers_2_int = TRUE,
86 .identifier_2_string = FALSE,
88 .symbol_2_token = FALSE,
89 .scope_0_fallback = FALSE,
93 p = g_slice_new0(ObActionParser);
95 p->scan = g_scanner_new(&config);
96 p->scan->input_name = source;
97 p->scan->msg_handler = display_error;
98 p->scan->user_data = p;
102 void action_parser_ref(ObActionParser *p)
107 void action_parser_unref(ObActionParser *p)
109 if (p && --p->ref < 1) {
110 g_scanner_destroy(p->scan);
111 g_slice_free(ObActionParser, p);
115 ObActionList* action_parser_read_string(ObActionParser *p, const gchar *text)
119 g_scanner_input_text(p->scan, text, strlen(text));
120 if (!p->scan->input_name)
121 p->scan->input_name = "(console)";
124 return parse_list(p, G_TOKEN_EOF, &e);
127 ObActionList* actions_parser_read_file(ObActionParser *p,
134 ch = g_io_channel_new_file(file, "r", error);
135 if (!ch) return NULL;
137 g_scanner_input_file(p->scan, g_io_channel_unix_get_fd(ch));
138 p->scan->input_name = file;
141 return parse_list(p, G_TOKEN_EOF, &e);
144 void action_parser_set_on_error(ObActionParser *p, ObActionParserErrorFunc f,
148 p->on_error_user_data = data;
151 const ObActionParserError* action_parser_get_last_error(void)
153 return last_error.message ? &last_error : NULL;
156 void action_parser_reset_last_error(void)
158 g_free(last_error.source);
159 g_free(last_error.message);
160 last_error.source = last_error.message = NULL;
163 static void display_error(GScanner *scanner, gchar *message, gboolean error)
165 ObActionParser *p = (ObActionParser*) scanner->user_data;
166 guint line = scanner->line;
167 if (!p->on_error || p->on_error(&line, message, p->on_error_user_data)) {
168 action_parser_reset_last_error();
170 last_error.source = g_strdup(scanner->input_name);
171 last_error.line = line;
172 last_error.message = g_strdup(message);
173 last_error.is_error = error;
174 g_message("%s:%u: %s: %s",
177 (last_error.is_error ? "error" : "warning"),
182 gpointer parse_error(ObActionParser *p, GTokenType exp, const gchar *message,
185 g_scanner_unexp_token(p->scan, exp, NULL, NULL, NULL, message, TRUE);
190 /************* Parser functions. The list is the entry point. ************
191 BNF for the language:
193 TEST := KEY=VALUE | KEY
194 ACTION := [FILTER] ACTION ELSE END | ACTIONNAME ACTIONOPTS | {ACTIONLIST}
195 ELSE := nil | \| ACTION
197 ACTIONLIST := ACTION ACTIONLIST | ACTION
199 FILTERORS := FILTERANDS \| FILTERORS | FILTERANDS
200 FILTERANDS := TEST, FILTERANDS | TEST
201 ACTIONOPTS := ACTIONOPT ACTIONOPTS | ACTIONOPT
202 ACTIONOPT := ATTRIBUTE:WORD | ATTRIBUTE:STRING | ATTRIBUTE:{ACTIONLIST}
203 WORD := string of text without any spaces
204 STRING := "TEXT" | (TEXT) |
205 where TEXT is a string, any occurance of the closing quote character must
206 be escaped by an backslash.
207 \\ \( \) and \" are all valid escaped characters.
208 ************** ************/
210 ObActionList* parse_list(ObActionParser *p, GTokenType end, gboolean *e)
212 ObActionList *first, *last;
217 t = g_scanner_peek_next_token(p->scan);
218 while (t != end && t != G_TOKEN_EOF) {
220 g_scanner_get_next_token(p->scan); /* skip empty lines */
222 g_scanner_get_next_token(p->scan); /* separator */
224 else if (t == G_TOKEN_IDENTIFIER || t == '[' || t == '{') {
227 /* parse the next action and stick it on the end of the list */
228 next = parse_action(p, e);
229 if (last) last->next = next;
230 if (!first) first = next;
234 g_scanner_get_next_token(p->scan);
235 parse_error(p, (end ? end : G_TOKEN_NONE),
236 _("Expected an action or end of action list"), e);
239 if (*e) break; /* don't parse any more after an error */
241 t = g_scanner_peek_next_token(p->scan);
244 /* eat the ending character */
245 g_scanner_get_next_token(p->scan);
250 ObActionList* parse_action(ObActionParser *p, gboolean *e)
254 gchar *name, *error_msg;
257 t = g_scanner_get_next_token(p->scan);
259 if (t == '[') return parse_filter(p, e);
260 if (t == '{') return parse_list(p, '}', e);
262 /* check for a name */
263 if (t != G_TOKEN_IDENTIFIER)
264 return parse_error(p, G_TOKEN_NONE, _("Expected an action name"), e);
266 /* read the action's name */
267 name = g_strdup(p->scan->value.v_string);
268 config = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
269 (GDestroyNotify)config_value_unref);
271 /* read the action's options key:value pairs */
272 t = g_scanner_peek_next_token(p->scan);
273 while (t == G_TOKEN_IDENTIFIER || t == '\\') {
275 g_scanner_get_next_token(p->scan); /* eat it */
276 t = g_scanner_get_next_token(p->scan); /* check for '\n' */
278 parse_error(p, G_TOKEN_NONE, _("Expected newline"), e);
282 ObConfigValue *value;
284 g_scanner_get_next_token(p->scan); /* eat the key */
285 t = g_scanner_peek_next_token(p->scan); /* check for ':' */
287 g_scanner_get_next_token(p->scan);
288 parse_error(p, ':', NULL, e);
289 break; /* don't read any more options */
293 key = g_strdup(p->scan->value.v_string);
294 g_scanner_get_next_token(p->scan); /* eat the ':' */
297 value = parse_value(p, TRUE, e);
299 /* check if we read a value (regardless of errors), and save
300 the key:value pair if we did. */
302 g_hash_table_replace(config, key, value);
304 g_free(key); /* didn't read any value */
307 if (*e) break; /* don't parse any more if there was an error */
309 t = g_scanner_peek_next_token(p->scan);
314 al = g_slice_new(ObActionList);
316 al->isfilterset = FALSE;
317 al->u.action = action_new(name, config, &error_msg);
320 g_hash_table_unref(config);
323 display_error(p->scan, error_msg, TRUE);
331 ObActionList* parse_filter(ObActionParser *p, gboolean *e)
334 ObActionList *al, *thendo, *elsedo;
335 ObActionListTest *test;
337 /* read the filter tests */
338 test = parse_filter_test(p, e);
340 action_list_test_destroy(test);
344 /* read the action for the filter */
345 thendo = parse_action(p, e);
349 /* check for else case */
350 t = g_scanner_peek_next_token(p->scan);
352 g_scanner_get_next_token(p->scan); /* eat it */
354 /* read the else action for the filter */
355 elsedo = parse_action(p, e);
359 al = g_slice_new(ObActionList);
361 al->isfilterset = TRUE;
363 al->u.f.thendo = thendo;
364 al->u.f.elsedo = elsedo;
369 ObActionListTest* parse_filter_test(ObActionParser *p, gboolean *e)
373 ObConfigValue *value;
375 ObActionFilter *filter;
376 ObActionListTest *next;
378 t = g_scanner_get_next_token(p->scan);
379 if (t == ']') /* empty */
382 else if (t != G_TOKEN_IDENTIFIER)
383 return parse_error(p, G_TOKEN_NONE,
384 _("Expected a filter test lvalue"), e);
387 key = g_strdup(p->scan->value.v_string);
392 /* check if it has a value also */
393 t = g_scanner_peek_next_token(p->scan);
395 g_scanner_get_next_token(p->scan); /* eat the = */
396 value = parse_value(p, FALSE, e);
399 /* don't allow any errors (but value can be null if not present) */
402 config_value_unref(value);
406 filter = action_filter_new(key, value);
409 m = g_strdup_printf(_("Unable to create filter: %s"), key);
410 parse_error(p, G_TOKEN_NONE, m, e);
415 config_value_unref(value);
419 /* check if there is another test and how we're connected */
420 t = g_scanner_get_next_token(p->scan);
421 if (t == ',') { /* and */
423 next = parse_filter_test(p, e);
425 else if (t == '|') { /* or */
427 next = parse_filter_test(p, e);
429 else if (t == ']') /* end of the filter */
432 parse_error(p, ']', NULL, e);
434 /* don't allow any errors */
436 action_filter_unref(filter);
437 action_list_test_destroy(next);
441 ObActionListTest *test;
443 test = g_slice_new(ObActionListTest);
444 test->filter = filter;
451 ObConfigValue* parse_value(ObActionParser *p,
452 gboolean allow_actions,
459 t = g_scanner_get_next_token(p->scan);
460 if (t == G_TOKEN_IDENTIFIER) {
461 v = config_value_new_string(p->scan->value.v_string);
464 v = config_value_new_string(parse_string(p, '"', e));
466 v = config_value_new_string(parse_string(p, ')', e));
467 else if (t == '{' && allow_actions) {
468 ObActionList *l = parse_list(p, '}', e);
469 if (l) v = config_value_new_action_list(l);
472 parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e);
477 gchar* parse_string(ObActionParser *p, guchar end, gboolean *e)
481 const gchar *error_message = NULL;
483 /* inside a string we want everything to be parsed as text (identifiers) */
484 p->scan->config->cset_skip_characters = "";
485 p->scan->config->cset_identifier_first = IDENTIFIER_NTH " ";
486 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH " ";
488 buf = g_string_new("");
490 t = g_scanner_get_next_token(p->scan);
492 if (t == G_TOKEN_IDENTIFIER)
493 g_string_append(buf, p->scan->value.v_string);
494 else if (t == G_TOKEN_EOF) {
495 error_message = _("Missing end of quoted string");
496 goto parse_string_error;
498 else if (t == '\\') { /* escape sequence */
499 t = g_scanner_get_next_token(p->scan);
500 if (!strchr(ESCAPE_SEQS, t)) {
501 error_message = _("Unknown escape sequence");
502 goto parse_string_error;
504 g_string_append_c(buf, t);
506 else /* other single character */
507 g_string_append_c(buf, t);
509 t = g_scanner_get_next_token(p->scan);
512 /* reset to their default values */
513 p->scan->config->cset_skip_characters = SKIP;
514 p->scan->config->cset_identifier_first = IDENTIFIER_FIRST;
515 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH;
519 g_string_free(buf, FALSE);
524 g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL,
525 error_message, TRUE);
526 g_string_free(buf, TRUE);