| |
1 /* |
| |
2 * libtu/parser.c |
| |
3 * |
| |
4 * Copyright (c) Tuomo Valkonen 1999-2000. |
| |
5 * |
| |
6 * This file is distributed under the terms of the "Artistic License". |
| |
7 * See the included file LICENSE for details. |
| |
8 */ |
| |
9 |
| |
10 #include <string.h> |
| |
11 |
| |
12 #include "include/parser.h" |
| |
13 #include "include/misc.h" |
| |
14 #include "include/output.h" |
| |
15 |
| |
16 #define MAX_TOKENS 32 |
| |
17 #define MAX_NEST 16 |
| |
18 |
| |
19 |
| |
20 enum{ |
| |
21 P_EOF, |
| |
22 P_OK, |
| |
23 P_ERROR, |
| |
24 P_BEG_SECT, |
| |
25 P_END_SECT |
| |
26 }; |
| |
27 |
| |
28 |
| |
29 static bool check_args(const Tokenizer *tokz, Token *tokens, int ntokens, |
| |
30 const char *fmt); |
| |
31 |
| |
32 |
| |
33 /* */ |
| |
34 |
| |
35 |
| |
36 static int read_statement(const ConfOpt **opt_ret, Token *tokens, |
| |
37 int *ntok_ret, Tokenizer *tokz, |
| |
38 const ConfOpt *options) |
| |
39 { |
| |
40 int ntokens=0; |
| |
41 Token *tok=NULL; |
| |
42 const ConfOpt *opt=NULL; |
| |
43 int had_comma=0; |
| |
44 int retval=P_OK; |
| |
45 int e=0; |
| |
46 int e_line=0; |
| |
47 |
| |
48 while(1){ |
| |
49 if(ntokens==MAX_TOKENS-1){ |
| |
50 e=E_TOKZ_TOKEN_LIMIT; |
| |
51 goto ret_err; |
| |
52 } |
| |
53 |
| |
54 tok=&tokens[ntokens]; |
| |
55 |
| |
56 if(!tokz_get_token(tokz, tok)) |
| |
57 goto ret_err; |
| |
58 |
| |
59 ntokens++; |
| |
60 |
| |
61 if(!TOK_IS_OP(tok)){ |
| |
62 if(ntokens==1 && !had_comma){ |
| |
63 if(!TOK_IS_IDENT(tok)){ |
| |
64 e=E_TOKZ_IDENTIFIER_EXPECTED; |
| |
65 goto ret_err; |
| |
66 } |
| |
67 |
| |
68 /* find the option */ |
| |
69 for(opt=options; opt->optname; opt++){ |
| |
70 if(strcmp(opt->optname, TOK_IDENT_VAL(tok))==0) |
| |
71 break; |
| |
72 } |
| |
73 |
| |
74 if(!opt->optname){ |
| |
75 e=E_TOKZ_UNKNOWN_OPTION; |
| |
76 e_line=tok->line; |
| |
77 retval=P_ERROR; |
| |
78 } |
| |
79 |
| |
80 had_comma=2; |
| |
81 }else{ |
| |
82 if(!had_comma){ |
| |
83 e=E_TOKZ_SYNTAX; |
| |
84 goto ret_err; |
| |
85 } |
| |
86 |
| |
87 had_comma=0; |
| |
88 } |
| |
89 continue; |
| |
90 } |
| |
91 |
| |
92 /* It is an operator */ |
| |
93 |
| |
94 switch(TOK_OP_VAL(tok)){ |
| |
95 case OP_SCOLON: |
| |
96 if(opt){ |
| |
97 if(had_comma || opt->opts){ |
| |
98 e=E_TOKZ_SYNTAX; |
| |
99 goto ret_err; |
| |
100 } |
| |
101 goto ret_success; |
| |
102 } |
| |
103 break; |
| |
104 |
| |
105 case OP_NEXTLINE: |
| |
106 if(had_comma==1){ |
| |
107 e=E_TOKZ_SYNTAX; |
| |
108 e_line=tok->line-1; |
| |
109 goto ret_err2; |
| |
110 } |
| |
111 |
| |
112 if(opt && !opt->opts) |
| |
113 goto ret_success; |
| |
114 break; |
| |
115 |
| |
116 case OP_EOF: |
| |
117 if(had_comma==1){ |
| |
118 e=E_TOKZ_UNEXPECTED_EOF; |
| |
119 goto ret_err; |
| |
120 } |
| |
121 |
| |
122 if(opt && opt->opts){ |
| |
123 e=E_TOKZ_UNEXPECTED_EOF; |
| |
124 goto ret_err; |
| |
125 } |
| |
126 |
| |
127 retval=P_EOF; |
| |
128 goto ret_success; |
| |
129 |
| |
130 case OP_R_BRC: |
| |
131 if(had_comma==1){ |
| |
132 e=E_TOKZ_SYNTAX; |
| |
133 goto ret_err; |
| |
134 } |
| |
135 |
| |
136 if(opt && opt->opts){ |
| |
137 e=E_TOKZ_SYNTAX; |
| |
138 goto ret_err; |
| |
139 } |
| |
140 |
| |
141 retval=P_END_SECT; |
| |
142 goto ret_success; |
| |
143 |
| |
144 case OP_L_BRC: |
| |
145 if(had_comma==1 || !opt || !opt->opts){ |
| |
146 e=E_TOKZ_SYNTAX; |
| |
147 goto ret_err; |
| |
148 } |
| |
149 |
| |
150 retval=P_BEG_SECT; |
| |
151 goto ret_success; |
| |
152 |
| |
153 case OP_COMMA: |
| |
154 if(had_comma){ |
| |
155 e=E_TOKZ_SYNTAX; |
| |
156 goto ret_err; |
| |
157 } |
| |
158 had_comma=1; |
| |
159 break; |
| |
160 |
| |
161 default: |
| |
162 e=E_TOKZ_SYNTAX; |
| |
163 goto ret_err; |
| |
164 } |
| |
165 |
| |
166 ntokens--; |
| |
167 } |
| |
168 |
| |
169 ret_err: |
| |
170 e_line=tok->line; |
| |
171 ret_err2: |
| |
172 retval=P_ERROR; |
| |
173 |
| |
174 ret_success: |
| |
175 if(retval==P_ERROR && e!=0) |
| |
176 tokz_warn_error(tokz, e_line, e); |
| |
177 |
| |
178 *opt_ret=opt; |
| |
179 *ntok_ret=ntokens; |
| |
180 |
| |
181 return retval; |
| |
182 } |
| |
183 |
| |
184 |
| |
185 /* */ |
| |
186 |
| |
187 |
| |
188 static bool call_end_sect(Tokenizer *tokz, const ConfOpt *options) |
| |
189 { |
| |
190 bool retval=TRUE; |
| |
191 |
| |
192 while(options->optname){ |
| |
193 if(strcmp(options->optname, "#end")==0){ |
| |
194 retval=options->fn(tokz, 0, NULL); |
| |
195 break; |
| |
196 } |
| |
197 options++; |
| |
198 } |
| |
199 |
| |
200 return retval; |
| |
201 } |
| |
202 |
| |
203 |
| |
204 static void call_cancel_sect(Tokenizer *tokz, const ConfOpt *options) |
| |
205 { |
| |
206 while(options->optname){ |
| |
207 if(strcmp(options->optname, "#cancel")==0){ |
| |
208 options->fn(tokz, 0, NULL); |
| |
209 break; |
| |
210 } |
| |
211 options++; |
| |
212 } |
| |
213 } |
| |
214 |
| |
215 |
| |
216 /* */ |
| |
217 |
| |
218 |
| |
219 /* Does the parsing work |
| |
220 */ |
| |
221 bool parse_config_tokz(Tokenizer *tokz, const ConfOpt *options) |
| |
222 { |
| |
223 Token tokens[MAX_TOKENS]; |
| |
224 bool alloced_optstack=FALSE; |
| |
225 int i, t, ntokens; |
| |
226 int init_nest_lvl; |
| |
227 bool had_error=FALSE; |
| |
228 |
| |
229 /* Allocate tokz->optstack if it does not yet exist (if it does, |
| |
230 * we have been called from an option handler) |
| |
231 */ |
| |
232 if(!tokz->optstack){ |
| |
233 tokz->optstack=ALLOC_N(const ConfOpt*, MAX_NEST); |
| |
234 if(!tokz->optstack){ |
| |
235 warn_err(); |
| |
236 return FALSE; |
| |
237 } |
| |
238 |
| |
239 memset(tokz->optstack, 0, sizeof(ConfOpt*)*MAX_NEST); |
| |
240 init_nest_lvl=tokz->nest_lvl=0; |
| |
241 alloced_optstack=TRUE; |
| |
242 }else{ |
| |
243 init_nest_lvl=tokz->nest_lvl; |
| |
244 } |
| |
245 |
| |
246 tokz->optstack[init_nest_lvl]=options; |
| |
247 |
| |
248 for(i=0;i<MAX_TOKENS;i++) |
| |
249 tok_init(&tokens[i]); |
| |
250 |
| |
251 |
| |
252 /* The loop |
| |
253 */ |
| |
254 while(1){ |
| |
255 t=read_statement(&options, tokens, &ntokens, |
| |
256 tokz, tokz->optstack[tokz->nest_lvl]); |
| |
257 |
| |
258 had_error=(t==P_ERROR); |
| |
259 |
| |
260 /* Check that arguments are ok */ |
| |
261 if(!had_error && options) |
| |
262 had_error=!check_args(tokz, tokens, ntokens, options->argfmt); |
| |
263 |
| |
264 if(tokz->flags&TOKZ_PARSER_INDENT_MODE) |
| |
265 verbose_indent(tokz->nest_lvl); |
| |
266 |
| |
267 /* New section? */ |
| |
268 if(t==P_BEG_SECT){ |
| |
269 if(tokz->nest_lvl==MAX_NEST-1){ |
| |
270 tokz_warn_error(tokz, tokz->line, E_TOKZ_MAX_NEST); |
| |
271 had_error=TRUE; |
| |
272 while(ntokens--) |
| |
273 tok_free(&tokens[ntokens]); |
| |
274 break; |
| |
275 }else{ |
| |
276 tokz->optstack[++tokz->nest_lvl]=options->opts; |
| |
277 } |
| |
278 } |
| |
279 |
| |
280 /* call the handler */ |
| |
281 if(!had_error && options && options->fn) |
| |
282 had_error=!options->fn(tokz, ntokens-1, tokens); |
| |
283 |
| |
284 /* free the tokens */ |
| |
285 while(ntokens--) |
| |
286 tok_free(&tokens[ntokens]); |
| |
287 |
| |
288 switch(t){ |
| |
289 case P_BEG_SECT: |
| |
290 if(!had_error) |
| |
291 continue; |
| |
292 /* #cancel handler should not be called when |
| |
293 * error occured in section begin handler */ |
| |
294 tokz->nest_lvl--; |
| |
295 break; |
| |
296 |
| |
297 case P_EOF: |
| |
298 if(tokz->nest_lvl>0){ |
| |
299 tokz_warn_error(tokz, 0, E_TOKZ_UNEXPECTED_EOF); |
| |
300 had_error=TRUE; |
| |
301 }else if(!had_error){ |
| |
302 had_error=!call_end_sect(tokz, tokz->optstack[0]); |
| |
303 } |
| |
304 break; |
| |
305 |
| |
306 case P_END_SECT: |
| |
307 if(tokz->nest_lvl==0){ |
| |
308 tokz_warn_error(tokz, tokz->line, E_TOKZ_SYNTAX); |
| |
309 had_error=TRUE; |
| |
310 break; |
| |
311 } |
| |
312 |
| |
313 if(!had_error) |
| |
314 had_error=!call_end_sect(tokz, tokz->optstack[tokz->nest_lvl]); |
| |
315 |
| |
316 tokz->nest_lvl--; |
| |
317 |
| |
318 if(tokz->nest_lvl<init_nest_lvl) |
| |
319 break; |
| |
320 |
| |
321 /* fall thru */ |
| |
322 |
| |
323 default: |
| |
324 if(!had_error) |
| |
325 continue; |
| |
326 } |
| |
327 break; |
| |
328 } |
| |
329 |
| |
330 /* On error, call all the #cancel-handlers */ |
| |
331 while(had_error && tokz->nest_lvl>=init_nest_lvl){ |
| |
332 call_cancel_sect(tokz, tokz->optstack[tokz->nest_lvl]); |
| |
333 tokz->nest_lvl--; |
| |
334 } |
| |
335 |
| |
336 /* Free optstack if it was alloced by this call */ |
| |
337 if(alloced_optstack){ |
| |
338 free(tokz->optstack); |
| |
339 tokz->optstack=NULL; |
| |
340 tokz->nest_lvl=0; |
| |
341 } |
| |
342 |
| |
343 if(tokz->flags&TOKZ_PARSER_INDENT_MODE) |
| |
344 verbose_indent(init_nest_lvl); |
| |
345 |
| |
346 return !had_error; |
| |
347 } |
| |
348 |
| |
349 |
| |
350 /* */ |
| |
351 |
| |
352 |
| |
353 bool parse_config(const char *fname, const ConfOpt *options) |
| |
354 { |
| |
355 Tokenizer *tokz; |
| |
356 bool ret; |
| |
357 |
| |
358 tokz=tokz_open(fname); |
| |
359 |
| |
360 if(tokz==NULL) |
| |
361 return FALSE; |
| |
362 |
| |
363 ret=parse_config_tokz(tokz, options); |
| |
364 |
| |
365 tokz_close(tokz); |
| |
366 |
| |
367 return ret; |
| |
368 } |
| |
369 |
| |
370 |
| |
371 bool parse_config_file(FILE *file, const ConfOpt *options) |
| |
372 { |
| |
373 Tokenizer *tokz; |
| |
374 bool ret; |
| |
375 |
| |
376 tokz=tokz_open_file(file); |
| |
377 |
| |
378 if(tokz==NULL) |
| |
379 return FALSE; |
| |
380 |
| |
381 ret=parse_config_tokz(tokz, options); |
| |
382 |
| |
383 tokz_close(tokz); |
| |
384 |
| |
385 return ret; |
| |
386 } |
| |
387 |
| |
388 |
| |
389 /* |
| |
390 * Argument validity checking stuff |
| |
391 */ |
| |
392 |
| |
393 |
| |
394 static bool arg_match(Token *tok, char c) |
| |
395 { |
| |
396 static const char chs[]={0, 'l', 'd', 'c', 's', 'i', 0, 0}; |
| |
397 char c2; |
| |
398 |
| |
399 if(c=='.' || c=='*') |
| |
400 return TRUE; |
| |
401 |
| |
402 c2=chs[tok->type]; |
| |
403 |
| |
404 if(c2==c) |
| |
405 return TRUE; |
| |
406 |
| |
407 if(c2=='c' && c=='l'){ |
| |
408 TOK_SET_LONG(tok, TOK_CHAR_VAL(tok)); |
| |
409 return TRUE; |
| |
410 } |
| |
411 |
| |
412 if(c2=='l' && c=='c'){ |
| |
413 TOK_SET_CHAR(tok, TOK_LONG_VAL(tok)); |
| |
414 return TRUE; |
| |
415 } |
| |
416 |
| |
417 if(c2=='l' && c=='d'){ |
| |
418 TOK_SET_DOUBLE(tok, TOK_LONG_VAL(tok)); |
| |
419 return TRUE; |
| |
420 } |
| |
421 |
| |
422 return FALSE; |
| |
423 } |
| |
424 |
| |
425 |
| |
426 static bool check_argument(const char **pret, Token *tok, const char *p) |
| |
427 { |
| |
428 int mode=0; |
| |
429 |
| |
430 if(*p=='*'){ |
| |
431 *pret=p; |
| |
432 return TRUE; |
| |
433 }else if(*p=='?'){ |
| |
434 mode=1; |
| |
435 p++; |
| |
436 }else if(*p==':'){ |
| |
437 mode=2; |
| |
438 p++; |
| |
439 }else if(*p=='+'){ |
| |
440 *pret=p; |
| |
441 return arg_match(tok, *(p-1)); |
| |
442 } |
| |
443 |
| |
444 while(*p!='\0'){ |
| |
445 if(arg_match(tok, *p)){ |
| |
446 p++; |
| |
447 while(mode==2 && *p==':'){ |
| |
448 if(*++p=='\0') |
| |
449 break; /* invalid argument format string it is... */ |
| |
450 p++; |
| |
451 } |
| |
452 *pret=p; |
| |
453 return TRUE; |
| |
454 } |
| |
455 |
| |
456 if(mode==0) |
| |
457 break; |
| |
458 |
| |
459 p++; |
| |
460 |
| |
461 if(mode==1){ |
| |
462 *pret=p; |
| |
463 return TRUE; |
| |
464 } |
| |
465 |
| |
466 if(*p!=':') |
| |
467 break; |
| |
468 p++; |
| |
469 } |
| |
470 |
| |
471 *pret=p; |
| |
472 return FALSE; |
| |
473 } |
| |
474 |
| |
475 |
| |
476 static bool args_at_end(const char *p) |
| |
477 { |
| |
478 if(p==NULL) |
| |
479 return TRUE; |
| |
480 |
| |
481 while(*p!='\0'){ |
| |
482 if(*p=='*' || *p=='+') |
| |
483 p++; |
| |
484 else if(*p=='?') |
| |
485 p+=2; |
| |
486 else |
| |
487 return FALSE; |
| |
488 } |
| |
489 |
| |
490 return TRUE; |
| |
491 } |
| |
492 |
| |
493 |
| |
494 static bool check_args(const Tokenizer *tokz, Token *tokens, int ntokens, |
| |
495 const char *fmt) |
| |
496 { |
| |
497 int i; |
| |
498 |
| |
499 if(fmt==NULL) |
| |
500 return ntokens==2; |
| |
501 |
| |
502 for(i=1; i<ntokens-1; i++){ |
| |
503 if(!check_argument(&fmt, &tokens[i], fmt)){ |
| |
504 tokz_warn_error(tokz, tokens[i].line, |
| |
505 *fmt!='\0' ? E_TOKZ_INVALID_ARGUMENT |
| |
506 : E_TOKZ_TOO_MANY_ARGS); |
| |
507 return FALSE; |
| |
508 } |
| |
509 } |
| |
510 |
| |
511 if(!args_at_end(fmt)){ |
| |
512 tokz_warn_error(tokz, tokens[i].line, E_TOKZ_TOO_FEW_ARGS); |
| |
513 return FALSE; |
| |
514 } |
| |
515 |
| |
516 return TRUE; |
| |
517 } |
| |
518 |