]> icculus.org git repositories - dana/openbox.git/blob - openbox/action_parser.c
Use ObConfigValue to parse gravity coords, remove parse special functions from config...
[dana/openbox.git] / openbox / action_parser.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    action_parser.c for the Openbox window manager
4    Copyright (c) 2011        Dana Jansens
5
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.
10
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.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "action_parser.h"
20 #include "action.h"
21 #include "action_filter.h"
22 #include "action_list.h"
23 #include "config_value.h"
24 #include "gettext.h"
25
26 #ifdef HAVE_STRING_H
27 #  include <string.h>
28 #endif
29
30 struct _ObActionList;
31 struct _ObActionListTest;
32
33 #define SKIP " \t"
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 "\"()"
37
38 struct _ObActionList* parse_list(ObActionParser *p,
39                                  GTokenType end,
40                                  gboolean *e);
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,
46                                    gboolean *e);
47 gchar* parse_string(ObActionParser *p, guchar end, gboolean *e);
48
49 struct _ObActionParser
50 {
51     gint ref;
52     GScanner *scan;
53 };
54
55 ObActionParser* action_parser_new(void)
56 {
57     ObActionParser *p;
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,
70         .scan_symbols = TRUE,
71         .scan_binary = FALSE,
72         .scan_octal = FALSE,
73         .scan_float = TRUE,
74         .scan_hex = FALSE,
75         .scan_hex_dollar = FALSE,
76         .scan_string_sq = FALSE,
77         .scan_string_dq = FALSE,
78         .numbers_2_int = TRUE,
79         .int_2_float = FALSE,
80         .identifier_2_string = FALSE,
81         .char_2_token = TRUE,
82         .symbol_2_token = FALSE,
83         .scope_0_fallback = FALSE,
84         .store_int64 = FALSE
85     };
86
87     p = g_slice_new(ObActionParser);
88     p->ref = 1;
89     p->scan = g_scanner_new(&config);
90     return p;
91 }
92
93 void action_parser_ref(ObActionParser *p)
94 {
95     ++p->ref;
96 }
97
98 void action_parser_unref(ObActionParser *p)
99 {
100     if (p && --p->ref < 1) {
101         g_scanner_destroy(p->scan);
102         g_slice_free(ObActionParser, p);
103     }
104 }
105
106 ObActionList* action_parser_read_string(ObActionParser *p, const gchar *text)
107 {
108     gboolean e;
109
110     g_scanner_input_text(p->scan, text, strlen(text));
111     p->scan->input_name = "(console)";
112
113     e = FALSE;
114     return parse_list(p, G_TOKEN_EOF, &e);
115 }
116
117 ObActionList* actions_parser_read_file(ObActionParser *p,
118                                        const gchar *file,
119                                        GError **error)
120 {
121     GIOChannel *ch;
122     gboolean e;
123
124     ch = g_io_channel_new_file(file, "r", error);
125     if (!ch) return NULL;
126
127     g_scanner_input_file(p->scan, g_io_channel_unix_get_fd(ch));
128     p->scan->input_name = file;
129
130     e = FALSE;
131     return parse_list(p, G_TOKEN_EOF, &e);
132 }
133
134 /*************  Parser functions.  The list is the entry point.  ************
135 BNF for the language:
136
137 TEST       := KEY=VALUE | KEY
138 ACTION     := [FILTER] ACTION ELSE END | ACTIONNAME ACTIONOPTS | {ACTIONLIST}
139 ELSE       := nil | \| ACTION
140 END        := \n | ; | EOF
141 ACTIONLIST := ACTION ACTIONLIST | ACTION
142 FILTER     := FILTERORS
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 **************                                                   ************/
153
154 gpointer parse_error(ObActionParser *p, GTokenType exp, const gchar *message,
155                      gboolean *e)
156 {
157     g_scanner_unexp_token(p->scan, exp, NULL, NULL, NULL, message, TRUE);
158     *e = TRUE;
159     return NULL;
160 }
161
162 ObActionList* parse_list(ObActionParser *p, GTokenType end, gboolean *e)
163 {
164     ObActionList *first, *last;
165     GTokenType t;
166
167     first = last = NULL;
168
169     t = g_scanner_peek_next_token(p->scan);
170     while (t != end && t != G_TOKEN_EOF) {
171         if (t == '\n')
172             g_scanner_get_next_token(p->scan); /* skip empty lines */
173         else if (t == ';') {
174             g_scanner_get_next_token(p->scan); /* separator */
175         }
176         else if (t == G_TOKEN_IDENTIFIER || t == '[' || t == '{') {
177             ObActionList *next;
178
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;
183             last = next;
184         }
185         else {
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);
189         }
190
191         if (*e) break; /* don't parse any more after an error */
192
193         t = g_scanner_peek_next_token(p->scan);
194     }
195
196     /* eat the ending character */
197     g_scanner_get_next_token(p->scan);
198
199     return first;
200 }
201
202 ObActionList* parse_action(ObActionParser *p, gboolean *e)
203 {
204     GTokenType t;
205     ObActionList *al;
206     gchar *name;
207     GHashTable *config;
208
209     t = g_scanner_get_next_token(p->scan);
210
211     if (t == '[') return parse_filter(p, e);
212     if (t == '{') return parse_list(p, '}', e);
213
214     /* check for a name */
215     if (t != G_TOKEN_IDENTIFIER)
216         return parse_error(p, G_TOKEN_NONE, _("Expected an action name"), e);
217
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);
222
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 == '\\') {
226         if (t == '\\') {
227             g_scanner_get_next_token(p->scan); /* eat it */
228             t = g_scanner_get_next_token(p->scan); /* check for '\n' */
229             if (t != '\n')
230                 parse_error(p, G_TOKEN_NONE, _("Expected newline"), e);
231         }
232         else {
233             gchar *key;
234             ObConfigValue *value;
235
236             g_scanner_get_next_token(p->scan); /* eat the key */
237             t = g_scanner_peek_next_token(p->scan); /* check for ':' */
238             if (t != ':') {
239                 g_scanner_get_next_token(p->scan);
240                 parse_error(p, ':', NULL, e);
241                 break; /* don't read any more options */
242             }
243
244             /* save the key */
245             key = g_strdup(p->scan->value.v_string);
246             g_scanner_get_next_token(p->scan); /* eat the ':' */
247
248             /* read the value */
249             value = parse_value(p, TRUE, e);
250
251             /* check if we read a value (regardless of errors), and save
252                the key:value pair if we did. */
253             if (value)
254                 g_hash_table_replace(config, key, value);
255             else
256                 g_free(key); /* didn't read any value */
257         }
258
259         if (*e) break; /* don't parse any more if there was an error */
260
261         t = g_scanner_peek_next_token(p->scan);
262     }
263
264     al = g_slice_new(ObActionList);
265     al->ref = 1;
266     al->isfilterset = FALSE;
267     al->u.action = action_new(name, config);
268     al->next = NULL;
269     g_free(name);
270     g_hash_table_unref(config);
271     return al;
272 }
273
274 ObActionList* parse_filter(ObActionParser *p, gboolean *e)
275 {
276     GTokenType t;
277     ObActionList *al, *thendo, *elsedo;
278     ObActionListTest *test;
279
280     /* read the filter tests */
281     test = parse_filter_test(p, e);
282     if (*e) {
283         action_list_test_destroy(test);
284         return NULL;
285     }
286
287     /* read the action for the filter */
288     thendo = parse_action(p, e);
289     elsedo = NULL;
290
291     if (!*e) {
292         /* check for else case */
293         t = g_scanner_peek_next_token(p->scan);
294         if (t == '|') {
295             g_scanner_get_next_token(p->scan); /* eat it */
296
297             /* read the else action for the filter */
298             elsedo = parse_action(p, e);
299         }
300     }
301
302     al = g_slice_new(ObActionList);
303     al->ref = 1;
304     al->isfilterset = TRUE;
305     al->u.f.test = test;
306     al->u.f.thendo = thendo;
307     al->u.f.elsedo = elsedo;
308     al->next = NULL;
309     return al;
310 }
311
312 ObActionListTest* parse_filter_test(ObActionParser *p, gboolean *e)
313 {
314     GTokenType t;
315     gchar *key;
316     ObConfigValue *value;
317     gboolean and;
318     ObActionFilter *filter;
319     ObActionListTest *next;
320
321     t = g_scanner_get_next_token(p->scan);
322     if (t == ']') /* empty */
323         return NULL;
324
325     else if (t != G_TOKEN_IDENTIFIER)
326         return parse_error(p, G_TOKEN_NONE,
327                            _("Expected a filter test lvalue"), e);
328
329     /* got a key */
330     key = g_strdup(p->scan->value.v_string);
331     value = NULL;
332     and = FALSE;
333     next = NULL;
334
335     /* check if it has a value also */
336     t = g_scanner_peek_next_token(p->scan);
337     if (t == '=') {
338         g_scanner_get_next_token(p->scan); /* eat the = */
339         value = parse_value(p, FALSE, e);
340     }
341
342     /* don't allow any errors (but value can be null if not present) */
343     if (*e) {
344         g_free(key);
345         config_value_unref(value);
346         return NULL;
347     }
348
349     filter = action_filter_new(key, value);
350     if (!filter) {
351         gchar *m;
352         m = g_strdup_printf(_("Unable to create filter: %s"), key);
353         parse_error(p, G_TOKEN_NONE, m, e);
354         g_free(m);
355     }
356
357     g_free(key);
358     config_value_unref(value);
359     if (!filter)
360         return NULL;
361
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 */
365         and = TRUE;
366         next = parse_filter_test(p, e);
367     }
368     else if (t == '|') { /* or */
369         and = FALSE;
370         next = parse_filter_test(p, e);
371     }
372     else if (t == ']') /* end of the filter */
373         ;
374     else
375         parse_error(p, ']', NULL, e);
376
377     /* don't allow any errors */
378     if (*e) {
379         action_filter_unref(filter);
380         action_list_test_destroy(next);
381         return NULL;
382     }
383     else {
384         ObActionListTest *test;
385
386         test = g_slice_new(ObActionListTest);
387         test->filter = filter;
388         test->and = and;
389         test->next = next;
390         return test;
391     }
392 }
393
394 ObConfigValue* parse_value(ObActionParser *p,
395                            gboolean allow_actions,
396                            gboolean *e)
397 {
398     GTokenType t;
399     ObConfigValue *v;
400
401     v = NULL;
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);
405     }
406     else if (t == '"')
407         v = config_value_new_string(parse_string(p, '"', e));
408     else if (t == '(')
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);
413     }
414     else
415         parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e);
416     return v;
417 }
418
419
420 gchar* parse_string(ObActionParser *p, guchar end, gboolean *e)
421 {
422     GString *buf;
423     GTokenType t;
424     const gchar *error_message = NULL;
425
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 " ";
430
431     buf = g_string_new("");
432
433     t = g_scanner_get_next_token(p->scan);
434     while (t != end) {
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;
440         }
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;
446             }
447             g_string_append_c(buf, t);
448         }
449         else /* other single character */
450             g_string_append_c(buf, t);
451
452         t = g_scanner_get_next_token(p->scan);
453     }
454
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;
459
460     {
461         gchar *v = buf->str;
462         g_string_free(buf, FALSE);
463         return v;
464     }
465
466 parse_string_error:
467     g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL,
468                           error_message, TRUE);
469     g_string_free(buf, TRUE);
470     *e = TRUE;
471     return NULL;
472 }