imported irssi.
[silc.git] / apps / irssi / src / lib-config / parse.c
1 /*
2  parse.c : irssi configuration - parse configuration file
3
4     Copyright (C) 1999 Timo Sirainen
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     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19 */
20
21 #include "module.h"
22
23 static int g_istr_equal(gconstpointer v, gconstpointer v2)
24 {
25         return g_strcasecmp((const char *) v, (const char *) v2) == 0;
26 }
27
28 /* a char* hash function from ASU */
29 static unsigned int g_istr_hash(gconstpointer v)
30 {
31         const char *s = (const char *) v;
32         unsigned int h = 0, g;
33
34         while (*s != '\0') {
35                 h = (h << 4) + toupper(*s);
36                 if ((g = h & 0xf0000000UL)) {
37                         h = h ^ (g >> 24);
38                         h = h ^ g;
39                 }
40                 s++;
41         }
42
43         return h /* % M */;
44 }
45
46 int config_error(CONFIG_REC *rec, const char *msg)
47 {
48         g_free_and_null(rec->last_error);
49         rec->last_error = g_strdup(msg);
50         return -1;
51 }
52
53 static int node_add_comment(CONFIG_NODE *parent, const char *str)
54 {
55         CONFIG_NODE *node;
56
57         g_return_val_if_fail(parent != NULL, -1);
58
59         if (!is_node_list(parent))
60                 return -1;
61
62         node = g_new0(CONFIG_NODE, 1);
63         node->type = NODE_TYPE_COMMENT;
64         node->value = str == NULL ? NULL : g_strdup(str);
65
66         parent->value = g_slist_append(parent->value, node);
67         return 0;
68 }
69
70 /* same as g_scanner_get_next_token() except skips and reads the comments */
71 static void config_parse_get_token(GScanner *scanner, CONFIG_NODE *node)
72 {
73         int prev_empty = FALSE;
74
75         for (;;) {
76                 g_scanner_get_next_token(scanner);
77
78                 if (scanner->token == G_TOKEN_COMMENT_SINGLE)
79                         node_add_comment(node, scanner->value.v_string);
80                 else if (scanner->token == '\n') {
81                         if (prev_empty) node_add_comment(node, NULL);
82                 } else {
83                         if (scanner->token == G_TOKEN_INT) {
84                                 scanner->token = G_TOKEN_STRING;
85 #undef g_strdup_printf /* This is free'd by GLib itself */
86                                 scanner->value.v_string = g_strdup_printf("%lu", scanner->value.v_int);
87 #ifdef MEM_DEBUG
88 #define g_strdup_printf(a, b...) ig_strdup_printf(__FILE__, __LINE__, a, ##b)
89 #endif
90                         }
91                         break;
92                 }
93
94                 prev_empty = TRUE;
95         }
96 }
97
98 /* same as g_scanner_peek_next_token() except skips and reads the comments */
99 static void config_parse_peek_token(GScanner *scanner, CONFIG_NODE *node)
100 {
101         int prev_empty = FALSE;
102
103         for (;;) {
104                 g_scanner_peek_next_token(scanner);
105
106                 if (scanner->next_token == G_TOKEN_COMMENT_SINGLE)
107                         node_add_comment(node, scanner->next_value.v_string);
108                 else if (scanner->next_token == '\n') {
109                         if (prev_empty) node_add_comment(node, NULL);
110                 } else
111                         break;
112
113                 prev_empty = TRUE;
114                 g_scanner_get_next_token(scanner);
115         }
116 }
117
118 /* get optional token, optionally warn if it's missing */
119 static void config_parse_warn_missing(CONFIG_REC *rec, CONFIG_NODE *node,
120                                       GTokenType expected_token, int print_warning)
121 {
122         config_parse_peek_token(rec->scanner, node);
123         if (rec->scanner->next_token == expected_token) {
124                 g_scanner_get_next_token(rec->scanner);
125                 return;
126         }
127
128         if (print_warning)
129                 g_scanner_warn(rec->scanner, "Warning: missing '%c'", expected_token);
130 }
131
132 static void config_parse_loop(CONFIG_REC *rec, CONFIG_NODE *node, GTokenType expect);
133
134 static GTokenType config_parse_symbol(CONFIG_REC *rec, CONFIG_NODE *node)
135 {
136         CONFIG_NODE *newnode;
137         GTokenType last_char;
138         int print_warning;
139         char *key;
140
141         g_return_val_if_fail(rec != NULL, G_TOKEN_ERROR);
142         g_return_val_if_fail(node != NULL, G_TOKEN_ERROR);
143
144         config_parse_get_token(rec->scanner, node);
145
146         last_char = (GTokenType) (node->type == NODE_TYPE_LIST ? ',' : ';');
147
148         /* key */
149         key = NULL;
150         if (node->type != NODE_TYPE_LIST &&
151             (rec->scanner->token == G_TOKEN_STRING)) {
152                 key = g_strdup(rec->scanner->value.v_string);
153
154                 config_parse_warn_missing(rec, node, '=', TRUE);
155                 config_parse_get_token(rec->scanner, node);
156         }
157
158         switch (rec->scanner->token) {
159         case G_TOKEN_STRING:
160                 /* value */
161                 config_node_set_str(rec, node, key, rec->scanner->value.v_string);
162                 g_free_not_null(key);
163
164                 print_warning = TRUE;
165                 if (node->type == NODE_TYPE_LIST) {
166                         /* if it's last item it doesn't need comma */
167                         config_parse_peek_token(rec->scanner, node);
168                         if (rec->scanner->next_token == ')')
169                                 print_warning = FALSE;
170                 }
171
172                 config_parse_warn_missing(rec, node, last_char, print_warning);
173                 break;
174
175         case '{':
176                 /* block */
177                 if (key == NULL && node->type != NODE_TYPE_LIST)
178                         return G_TOKEN_ERROR;
179
180                 newnode = config_node_section(node, key, NODE_TYPE_BLOCK);
181                 config_parse_loop(rec, newnode, (GTokenType) '}');
182                 g_free_not_null(key);
183
184                 config_parse_get_token(rec->scanner, node);
185                 if (rec->scanner->token != '}')
186                         return (GTokenType) '}';
187
188                 config_parse_warn_missing(rec, node, last_char, FALSE);
189                 break;
190
191         case '(':
192                 /* list */
193                 if (key == NULL)
194                         return G_TOKEN_ERROR;
195                 newnode = config_node_section(node, key, NODE_TYPE_LIST);
196                 config_parse_loop(rec, newnode, (GTokenType) ')');
197                 g_free_not_null(key);
198
199                 config_parse_get_token(rec->scanner, node);
200                 if (rec->scanner->token != ')')
201                         return (GTokenType) ')';
202
203                 config_parse_warn_missing(rec, node, last_char, FALSE);
204                 break;
205
206         default:
207                 /* error */
208                 g_free_not_null(key);
209                 return G_TOKEN_STRING;
210         }
211
212         return G_TOKEN_NONE;
213 }
214
215 static void config_parse_loop(CONFIG_REC *rec, CONFIG_NODE *node, GTokenType expect)
216 {
217         GTokenType expected_token;
218
219         g_return_if_fail(rec != NULL);
220         g_return_if_fail(node != NULL);
221
222         for (;;) {
223                 config_parse_peek_token(rec->scanner, node);
224                 if (rec->scanner->next_token == expect ||
225                     rec->scanner->next_token == G_TOKEN_EOF) break;
226
227                 expected_token = config_parse_symbol(rec, node);
228                 if (expected_token != G_TOKEN_NONE) {
229                         if (expected_token == G_TOKEN_ERROR)
230                                 expected_token = G_TOKEN_NONE;
231                         g_scanner_unexp_token(rec->scanner, expected_token, NULL, "symbol", NULL, NULL, TRUE);
232                 }
233         }
234 }
235
236 static void config_parse_error_func(GScanner *scanner, char *message, int is_error)
237 {
238         CONFIG_REC *rec = scanner->user_data;
239         char *old;
240
241         old = rec->last_error;
242         rec->last_error = g_strdup_printf("%s%s:%d: %s%s\n",
243                                           old == NULL ? "" : old,
244                                           scanner->input_name, scanner->line,
245                                           is_error ? "error: " : "",
246                                           message);
247         g_free_not_null(old);
248 }
249
250 void config_parse_init(CONFIG_REC *rec, const char *name)
251 {
252         GScanner *scanner;
253
254         g_free_and_null(rec->last_error);
255         config_nodes_remove_all(rec);
256
257         rec->scanner = scanner = g_scanner_new(NULL);
258         scanner->config->skip_comment_single = FALSE;
259         scanner->config->cset_skip_characters = " \t";
260         scanner->config->scan_binary = FALSE;
261         scanner->config->scan_octal = FALSE;
262         scanner->config->scan_float = FALSE;
263         scanner->config->scan_string_sq = TRUE;
264         scanner->config->scan_string_dq = TRUE;
265         scanner->config->scan_identifier_1char = TRUE;
266         scanner->config->identifier_2_string = TRUE;
267
268         scanner->user_data = rec;
269         scanner->input_name = name;
270         scanner->msg_handler = (GScannerMsgFunc) config_parse_error_func;
271 }
272
273 /* Parse configuration file */
274 int config_parse(CONFIG_REC *rec)
275 {
276         g_return_val_if_fail(rec != NULL, -1);
277         g_return_val_if_fail(rec->fname != NULL, -1);
278
279         rec->handle = open(rec->fname, O_RDONLY);
280         if (rec->handle == -1)
281                 return config_error(rec, g_strerror(errno));
282
283         config_parse_init(rec, rec->fname);
284         g_scanner_input_file(rec->scanner, rec->handle);
285         config_parse_loop(rec, rec->mainnode, G_TOKEN_EOF);
286         g_scanner_destroy(rec->scanner);
287
288         close(rec->handle);
289         rec->handle = -1;
290
291         return rec->last_error == NULL ? 0 : -1;
292 }
293
294 /* Parse configuration found from `data'. `input_name' is specifies the
295    "configuration name" which is displayed in error messages. */
296 int config_parse_data(CONFIG_REC *rec, const char *data, const char *input_name)
297 {
298         config_parse_init(rec, input_name);
299         g_scanner_input_text(rec->scanner, data, strlen(data));
300         config_parse_loop(rec, rec->mainnode, G_TOKEN_EOF);
301         g_scanner_destroy(rec->scanner);
302
303         return rec->last_error == NULL ? 0 : -1;
304 }
305
306 /* Open configuration. The file is created if it doesn't exist, unless
307    `create_mode' is -1. `fname' can be NULL if you just want to use
308    config_parse_data() */
309 CONFIG_REC *config_open(const char *fname, int create_mode)
310 {
311         CONFIG_REC *rec;
312         int f;
313
314         if (fname != NULL) {
315                 f = open(fname, O_RDONLY | (create_mode != -1 ? O_CREAT : 0), create_mode);
316                 if (f == -1) return NULL;
317                 close(f);
318         }
319
320         rec = g_new0(CONFIG_REC, 1);
321         rec->fname = fname == NULL ? NULL : g_strdup(fname);
322         rec->handle = -1;
323         rec->create_mode = create_mode;
324         rec->mainnode = g_new0(CONFIG_NODE, 1);
325         rec->mainnode->type = NODE_TYPE_BLOCK;
326         rec->cache = g_hash_table_new((GHashFunc) g_istr_hash, (GCompareFunc) g_istr_equal);
327         rec->cache_nodes = g_hash_table_new((GHashFunc) g_direct_hash, (GCompareFunc) g_direct_equal);
328
329         return rec;
330 }
331
332 /* Release all memory used by configuration */
333 void config_close(CONFIG_REC *rec)
334 {
335         g_return_if_fail(rec != NULL);
336
337         config_nodes_remove_all(rec);
338         g_free(rec->mainnode);
339
340         if (rec->handle != -1) close(rec->handle);
341         g_hash_table_foreach(rec->cache, (GHFunc) g_free, NULL);
342         g_hash_table_destroy(rec->cache);
343         g_hash_table_destroy(rec->cache_nodes);
344         g_free_not_null(rec->last_error);
345         g_free_not_null(rec->fname);
346         g_free(rec);
347 }
348
349 /* Change file name of config file */
350 void config_change_file_name(CONFIG_REC *rec, const char *fname, int create_mode)
351 {
352         g_return_if_fail(rec != NULL);
353         g_return_if_fail(fname != NULL);
354
355         g_free_not_null(rec->fname);
356         rec->fname = g_strdup(fname);
357
358         if (create_mode != -1)
359                 rec->create_mode = create_mode;
360 }