]> icculus.org git repositories - dana/openbox.git/blob - openbox/actions_parser.c
rm some unused fn defns
[dana/openbox.git] / openbox / actions_parser.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    actions_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 "actions_parser.h"
20 #include "actions.h"
21 #include "actions_list.h"
22 #include "actions_value.h"
23 #include "gettext.h"
24
25 #ifdef HAVE_STRING_H
26 #  include <string.h>
27 #endif
28
29 struct _ObActionsList;
30 struct _ObActionsListTest;
31 struct _ObActionsValue;
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 _ObActionsList* parse_list(ObActionsParser *p,
39                                   GTokenType end,
40                                   gboolean *e);
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,
46                                         gboolean *e);
47 gchar* parse_string(ObActionsParser *p, guchar end, gboolean *e);
48
49 struct _ObActionsParser
50 {
51     gint ref;
52     GScanner *scan;
53 };
54
55 ObActionsParser* actions_parser_new(void)
56 {
57     ObActionsParser *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(ObActionsParser);
88     p->ref = 1;
89     p->scan = g_scanner_new(&config);
90     return p;
91 }
92
93 void actions_parser_ref(ObActionsParser *p)
94 {
95     ++p->ref;
96 }
97
98 void actions_parser_unref(ObActionsParser *p)
99 {
100     if (p && --p->ref < 1) {
101         g_scanner_destroy(p->scan);
102         g_slice_free(ObActionsParser, p);
103     }
104 }
105
106 ObActionsList* actions_parser_read_string(ObActionsParser *p,
107                                           const gchar *text)
108 {
109     gboolean e;
110
111     g_scanner_input_text(p->scan, text, strlen(text));
112     p->scan->input_name = "(console)";
113
114     e = FALSE;
115     return parse_list(p, G_TOKEN_EOF, &e);
116 }
117
118 ObActionsList* actions_parser_read_file(ObActionsParser *p,
119                                         const gchar *file,
120                                         GError **error)
121 {
122     GIOChannel *ch;
123     gboolean e;
124
125     ch = g_io_channel_new_file(file, "r", error);
126     if (!ch) return NULL;
127
128     g_scanner_input_file(p->scan, g_io_channel_unix_get_fd(ch));
129     p->scan->input_name = file;
130
131     e = FALSE;
132     return parse_list(p, G_TOKEN_EOF, &e);
133 }
134
135 /*************  Parser functions.  The list is the entry point.  ************
136 BNF for the language:
137
138 TEST       := KEY=VALUE | KEY
139 ACTION     := [FILTER] ACTION ELSE END | ACTIONNAME ACTIONOPTS | {ACTIONLIST}
140 ELSE       := nil | \| ACTION
141 END        := \n | ; | EOF
142 ACTIONLIST := ACTION ACTIONLIST | ACTION
143 FILTER     := FILTERORS
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 **************                                                   ************/
154
155 gpointer parse_error(ObActionsParser *p, GTokenType exp, const gchar *message,
156                      gboolean *e)
157 {
158     g_scanner_unexp_token(p->scan, exp, NULL, NULL, NULL, message, TRUE);
159     *e = TRUE;
160     return NULL;
161 }
162
163 ObActionsList* parse_list(ObActionsParser *p, GTokenType end, gboolean *e)
164 {
165     ObActionsList *first, *last;
166     GTokenType t;
167
168     first = last = NULL;
169
170     t = g_scanner_peek_next_token(p->scan);
171     while (t != end && t != G_TOKEN_EOF) {
172         if (t == '\n')
173             g_scanner_get_next_token(p->scan); /* skip empty lines */
174         else if (t == ';') {
175             g_scanner_get_next_token(p->scan); /* separator */
176         }
177         else if (t == G_TOKEN_IDENTIFIER) {
178             ObActionsList *next;
179
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;
184             last = next;
185         }
186         else {
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);
190         }
191
192         if (*e) break; /* don't parse any more after an error */
193
194         t = g_scanner_peek_next_token(p->scan);
195     }
196
197     /* eat the ending character */
198     g_scanner_get_next_token(p->scan);
199
200     return first;
201 }
202
203 ObActionsList* parse_action(ObActionsParser *p, gboolean *e)
204 {
205     GTokenType t;
206     ObActionsList *al;
207     gchar *name;
208     GHashTable *config;
209
210     t = g_scanner_get_next_token(p->scan);
211
212     if (t == '[') return parse_filter(p, e);
213     if (t == '{') return parse_list(p, '}', e);
214
215     /* check for a name */
216     if (t != G_TOKEN_IDENTIFIER)
217         return parse_error(p, G_TOKEN_NONE, _("Expected an action name"), e);
218
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);
223
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 == '\\') {
227         if (t == '\\') {
228             g_scanner_get_next_token(p->scan); /* eat it */
229             t = g_scanner_get_next_token(p->scan); /* check for '\n' */
230             if (t != '\n')
231                 parse_error(p, G_TOKEN_NONE, _("Expected newline"), e);
232         }
233         else {
234             gchar *key;
235             ObActionsValue *value;
236
237             g_scanner_get_next_token(p->scan); /* eat the key */
238             t = g_scanner_peek_next_token(p->scan); /* check for ':' */
239             if (t != ':') {
240                 g_scanner_get_next_token(p->scan);
241                 parse_error(p, ':', NULL, e);
242                 break; /* don't read any more options */
243             }
244
245             /* save the key */
246             key = g_strdup(p->scan->value.v_string);
247             g_scanner_get_next_token(p->scan); /* eat the ':' */
248
249             /* read the value */
250             value = parse_value(p, TRUE, e);
251
252             /* check if we read a value (regardless of errors), and save
253                the key:value pair if we did. */
254             if (value)
255                 g_hash_table_replace(config, key, value);
256             else
257                 g_free(key); /* didn't read any value */
258         }
259
260         if (*e) break; /* don't parse any more if there was an error */
261
262         t = g_scanner_peek_next_token(p->scan);
263     }
264
265     al = g_slice_new(ObActionsList);
266     al->ref = 1;
267     al->isfilter = FALSE;
268     al->u.action = actions_act_new(name, config);
269     al->next = NULL;
270     g_free(name);
271     g_hash_table_unref(config);
272     return al;
273 }
274
275 ObActionsList* parse_filter(ObActionsParser *p, gboolean *e)
276 {
277     GTokenType t;
278     ObActionsList *al, *thendo, *elsedo;
279     ObActionsListTest *test;
280
281     /* read the filter tests */
282     test = parse_filter_test(p, e);
283     if (*e) {
284         actions_list_test_destroy(test);
285         return NULL;
286     }
287
288     /* read the action for the filter */
289     thendo = parse_action(p, e);
290     elsedo = NULL;
291
292     if (!*e) {
293         /* check for else case */
294         t = g_scanner_peek_next_token(p->scan);
295         if (t == '|') {
296             g_scanner_get_next_token(p->scan); /* eat it */
297
298             /* read the else action for the filter */
299             elsedo = parse_action(p, e);
300         }
301     }
302
303     al = g_slice_new(ObActionsList);
304     al->ref = 1;
305     al->isfilter = TRUE;
306     al->u.f.test = test;
307     al->u.f.thendo = thendo;
308     al->u.f.elsedo = elsedo;
309     al->next = NULL;
310     return al;
311 }
312
313 ObActionsListTest* parse_filter_test(ObActionsParser *p, gboolean *e)
314 {
315     GTokenType t;
316     gchar *key;
317     ObActionsValue *value;
318     gboolean and;
319     ObActionsListTest *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         actions_value_unref(value);
346         return NULL;
347     }
348
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 */
352         and = TRUE;
353         next = parse_filter_test(p, e);
354     }
355     else if (t == '|') { /* or */
356         and = FALSE;
357         next = parse_filter_test(p, e);
358     }
359     else if (t == ']') /* end of the filter */
360         ;
361     else
362         parse_error(p, ']', NULL, e);
363
364     /* don't allow any errors */
365     if (*e) {
366         g_free(key);
367         actions_value_unref(value);
368         actions_list_test_destroy(next);
369         return NULL;
370     }
371     else {
372         ObActionsListTest *test;
373
374         test = g_slice_new(ObActionsListTest);
375         test->key = key;
376         test->value = value;
377         test->and = and;
378         test->next = next;
379         return test;
380     }
381 }
382
383 ObActionsValue* parse_value(ObActionsParser *p,
384                                 gboolean allow_actions,
385                                 gboolean *e)
386 {
387     GTokenType t;
388     ObActionsValue *v;
389
390     v = NULL;
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);
394     }
395     else if (t == '"')
396         v = actions_value_new_string(parse_string(p, '"', e));
397     else if (t == '(')
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);
402     }
403     else
404         parse_error(p, G_TOKEN_NONE, _("Expected an option value"), e);
405     return v;
406 }
407
408
409 gchar* parse_string(ObActionsParser *p, guchar end, gboolean *e)
410 {
411     GString *buf;
412     GTokenType t;
413     const gchar *error_message = NULL;
414
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 " ";
419
420     buf = g_string_new("");
421
422     t = g_scanner_get_next_token(p->scan);
423     while (t != end) {
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;
429         }
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;
435             }
436             g_string_append_c(buf, t);
437         }
438         else /* other single character */
439             g_string_append_c(buf, t);
440
441         t = g_scanner_get_next_token(p->scan);
442     }
443
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;
448
449     {
450         gchar *v = buf->str;
451         g_string_free(buf, FALSE);
452         return v;
453     }
454
455 parse_string_error:
456     g_scanner_unexp_token(p->scan, G_TOKEN_NONE, NULL, NULL, NULL,
457                           error_message, TRUE);
458     g_string_free(buf, TRUE);
459     *e = TRUE;
460     return NULL;
461 }