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_list.h"
22 #include "action_value.h"
30 struct _ObActionListTest;
31 struct _ObActionValue;
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 _ObActionValue* 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) {
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)action_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 ObActionValue *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->isfilter = 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);
306 al->u.f.thendo = thendo;
307 al->u.f.elsedo = elsedo;
312 ObActionListTest* parse_filter_test(ObActionParser *p, gboolean *e)
316 ObActionValue *value;
318 ObActionListTest *next;
320 t = g_scanner_get_next_token(p->scan);
321 if (t == ']') /* empty */
324 else if (t != G_TOKEN_IDENTIFIER)
325 return parse_error(p, G_TOKEN_NONE,
326 _("Expected a filter test lvalue"), e);
329 key = g_strdup(p->scan->value.v_string);
334 /* check if it has a value also */
335 t = g_scanner_peek_next_token(p->scan);
337 g_scanner_get_next_token(p->scan); /* eat the = */
338 value = parse_value(p, FALSE, e);
341 /* don't allow any errors (but value can be null if not present) */
344 action_value_unref(value);
348 /* check if there is another test and how we're connected */
349 t = g_scanner_get_next_token(p->scan);
350 if (t == ',') { /* and */
352 next = parse_filter_test(p, e);
354 else if (t == '|') { /* or */
356 next = parse_filter_test(p, e);
358 else if (t == ']') /* end of the filter */
361 parse_error(p, ']', NULL, e);
363 /* don't allow any errors */
366 action_value_unref(value);
367 action_list_test_destroy(next);
371 ObActionListTest *test;
373 test = g_slice_new(ObActionListTest);
382 ObActionValue* parse_value(ObActionParser *p,
383 gboolean allow_actions,
390 t = g_scanner_get_next_token(p->scan);
391 if (t == G_TOKEN_IDENTIFIER) {
392 v = action_value_new_string(p->scan->value.v_string);
395 v = action_value_new_string(parse_string(p, '"', e));
397 v = action_value_new_string(parse_string(p, ')', e));
398 else if (t == '{' && allow_actions) {
399 ObActionList *l = parse_list(p, '}', e);
400 if (l) v = action_value_new_action_list(l);
403 parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e);
408 gchar* parse_string(ObActionParser *p, guchar end, gboolean *e)
412 const gchar *error_message = NULL;
414 /* inside a string we want everything to be parsed as text (identifiers) */
415 p->scan->config->cset_skip_characters = "";
416 p->scan->config->cset_identifier_first = IDENTIFIER_NTH " ";
417 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH " ";
419 buf = g_string_new("");
421 t = g_scanner_get_next_token(p->scan);
423 if (t == G_TOKEN_IDENTIFIER)
424 g_string_append(buf, p->scan->value.v_string);
425 else if (t == G_TOKEN_EOF) {
426 error_message = _("Missing end of quoted string");
427 goto parse_string_error;
429 else if (t == '\\') { /* escape sequence */
430 t = g_scanner_get_next_token(p->scan);
431 if (!strchr(ESCAPE_SEQS, t)) {
432 error_message = _("Unknown escape sequence");
433 goto parse_string_error;
435 g_string_append_c(buf, t);
437 else /* other single character */
438 g_string_append_c(buf, t);
440 t = g_scanner_get_next_token(p->scan);
443 /* reset to their default values */
444 p->scan->config->cset_skip_characters = SKIP;
445 p->scan->config->cset_identifier_first = IDENTIFIER_FIRST;
446 p->scan->config->cset_identifier_nth = IDENTIFIER_NTH;
450 g_string_free(buf, FALSE);
455 g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL,
456 error_message, TRUE);
457 g_string_free(buf, TRUE);