1 /*
2  * libtu/parser.c
3  *
4  * Copyright (c) Tuomo Valkonen 1999-2002.
5  *
6  * You may distribute and modify this library under the terms of either
7  * the Clarified Artistic License or the GNU LGPL, version 2.1 or later.
8  */
9 
10 #include <string.h>
11 #include <errno.h>
12 
13 #include <libtu/parser.h>
14 #include <libtu/misc.h>
15 #include <libtu/output.h>
16 
17 #define MAX_TOKENS 	256
18 #define MAX_NEST	256
19 
20 
21 enum{
22 	P_NONE=1,
23 	P_EOF,
24 	P_STMT,
25 	P_STMT_NS,
26 	P_STMT_SECT,
27 	P_BEG_SECT,
28 	P_END_SECT
29 };
30 
31 
32 /* */
33 
34 
35 static bool opt_include(Tokenizer *tokz, int n, Token *toks);
36 
37 
38 static ConfOpt common_opts[]={
39 	{"include", "s", opt_include, NULL},
40 	{NULL, NULL, NULL, NULL}
41 };
42 
43 
44 /* */
45 
46 
read_statement(Tokenizer * tokz,Token * tokens,int * ntok_ret)47 static int read_statement(Tokenizer *tokz, Token *tokens, int *ntok_ret)
48 {
49 	int ntokens=0;
50 	Token *tok=NULL;
51 	int had_comma=0; /* 0 - no, 1 - yes, 2 - not had, not expected */
52 	int retval=0;
53 	int e=0;
54 
55 	while(1){
56 		tok=&tokens[ntokens];
57 
58 		if(!tokz_get_token(tokz, tok)){
59 			e=1;
60 			continue;
61 		}
62 
63 		if(ntokens==MAX_TOKENS-1){
64 			e=E_TOKZ_TOKEN_LIMIT;
65 			tokz_warn_error(tokz, tok->line, e);
66 			if(!(tokz->flags&TOKZ_ERROR_TOLERANT))
67 				break;
68 		}else{
69 			ntokens++;
70 		}
71 
72 		if(!TOK_IS_OP(tok)){
73 			if(ntokens==1 && !had_comma){
74 				/* first token */
75 				had_comma=2;
76 			}else{
77 				if(had_comma==0)
78 					goto syntax;
79 
80 				had_comma=0;
81 			}
82 			continue;
83 		}
84 
85 		/* It is an operator */
86 		ntokens--;
87 
88 		switch(TOK_OP_VAL(tok)){
89 		case OP_SCOLON:
90 			retval=(ntokens==0 ? P_NONE : P_STMT_NS);
91 			break;
92 
93 		case OP_NEXTLINE:
94 			retval=(ntokens==0 ? P_NONE : P_STMT);
95 			break;
96 
97 		case OP_L_BRC:
98 			retval=(ntokens==0 ? P_BEG_SECT : P_STMT_SECT);
99 			break;
100 
101 		case OP_R_BRC:
102 			if(ntokens==0){
103 				retval=P_END_SECT;
104 			}else{
105 				tokz_unget_token(tokz, tok);
106 				retval=P_STMT_NS;
107 			}
108 			break;
109 
110 		case OP_EOF:
111 			retval=(ntokens==0 ? P_EOF : P_STMT_NS);
112 
113 			if(had_comma==1){
114 				e=E_TOKZ_UNEXPECTED_EOF;
115 				goto handle_error;
116 			}
117 
118 			goto end;
119 
120 		case OP_COMMA:
121 			if(had_comma!=0)
122 				goto syntax;
123 
124 			had_comma=1;
125 			continue;
126 
127 		default:
128 			goto syntax;
129 		}
130 
131 		if(had_comma!=1)
132 			break;
133 
134 	syntax:
135 		e=E_TOKZ_SYNTAX;
136 	handle_error:
137 		tokz_warn_error(tokz, tok->line, e);
138 
139 		if(!(tokz->flags&TOKZ_ERROR_TOLERANT) || retval!=0)
140 			break;
141 	}
142 
143 end:
144 	if(e!=0)
145 		retval=-retval;
146 
147 	*ntok_ret=ntokens;
148 
149 	return retval;
150 }
151 
152 
find_beg_sect(Tokenizer * tokz)153 static bool find_beg_sect(Tokenizer *tokz)
154 {
155 	Token tok=TOK_INIT;
156 
157 	while(tokz_get_token(tokz, &tok)){
158 		if(TOK_IS_OP(&tok)){
159 			if(TOK_OP_VAL(&tok)==OP_NEXTLINE)
160 				continue;
161 
162 			if(TOK_OP_VAL(&tok)==OP_SCOLON)
163 				return FALSE;
164 
165 			if(TOK_OP_VAL(&tok)==OP_L_BRC)
166 				return TRUE;
167 		}
168 
169 		tokz_unget_token(tokz, &tok);
170 		break;
171 	}
172 	return FALSE;
173 }
174 
175 
176 /* */
177 
178 
lookup_option(const ConfOpt * opts,const char * name)179 static const ConfOpt* lookup_option(const ConfOpt *opts, const char *name)
180 {
181 	while(opts->optname!=NULL){
182 		if(strcmp(opts->optname, name)==0)
183 			return opts;
184 		opts++;
185 	}
186 	return NULL;
187 }
188 
189 
call_end_sect(Tokenizer * tokz,const ConfOpt * opts)190 static bool call_end_sect(Tokenizer *tokz, const ConfOpt *opts)
191 {
192 	opts=lookup_option(opts, "#end");
193 	if(opts!=NULL)
194 		return opts->fn(tokz, 0, NULL);
195 
196 	return TRUE;
197 }
198 
199 
call_cancel_sect(Tokenizer * tokz,const ConfOpt * opts)200 static bool call_cancel_sect(Tokenizer *tokz, const ConfOpt *opts)
201 {
202 	opts=lookup_option(opts, "#cancel");
203 	if(opts!=NULL)
204 		return opts->fn(tokz, 0, NULL);
205 
206 	return TRUE;
207 }
208 
209 
210 /* */
211 
212 
parse_config_tokz(Tokenizer * tokz,const ConfOpt * options)213 bool parse_config_tokz(Tokenizer *tokz, const ConfOpt *options)
214 {
215 	Token tokens[MAX_TOKENS];
216 	bool alloced_optstack=FALSE;
217 	int i, t, ntokens=0;
218 	int init_nest_lvl;
219 	bool had_error;
220 	int errornest=0;
221 	bool is_default=FALSE;
222 
223 	/* Allocate tokz->optstack if it does not yet exist (if it does,
224 	 * we have been called from an option handler)
225 	 */
226 	if(!tokz->optstack){
227 		tokz->optstack=ALLOC_N(const ConfOpt*, MAX_NEST);
228 		if(!tokz->optstack){
229 			warn_err();
230 			return FALSE;
231 		}
232 
233 		memset(tokz->optstack, 0, sizeof(ConfOpt*)*MAX_NEST);
234 		init_nest_lvl=tokz->nest_lvl=0;
235 		alloced_optstack=TRUE;
236 	}else{
237 		init_nest_lvl=tokz->nest_lvl;
238 	}
239 
240 	tokz->optstack[init_nest_lvl]=options;
241 
242 	for(i=0; i<MAX_TOKENS; i++)
243 		tok_init(&tokens[i]);
244 
245 
246 	while(1){
247 		had_error=FALSE;
248 
249 		/* free the tokens */
250 		while(ntokens--)
251 			tok_free(&tokens[ntokens]);
252 
253 		/* read the tokens */
254 		t=read_statement(tokz, tokens, &ntokens);
255 
256 		if((had_error=t<0))
257 			t=-t;
258 
259 		switch(t){
260 		case P_STMT:
261 		case P_STMT_NS:
262 		case P_STMT_SECT:
263 
264 			if(errornest)
265 				had_error=TRUE;
266 			else if(tokz->flags&TOKZ_PARSER_INDENT_MODE)
267 				verbose_indent(tokz->nest_lvl);
268 
269 			if(!TOK_IS_IDENT(tokens+0)){
270 				had_error=TRUE;
271 				tokz_warn_error(tokz, tokens->line,
272 								E_TOKZ_IDENTIFIER_EXPECTED);
273 			}
274 
275 			if(t==P_STMT){
276 				if(find_beg_sect(tokz))
277 					t=P_STMT_SECT;
278 			}
279 
280 			if(had_error)
281 				break;
282 
283 			/* Got the statement and its type */
284 
285 			options=lookup_option(tokz->optstack[tokz->nest_lvl],
286 								  TOK_IDENT_VAL(tokens+0));
287 			if(options==NULL)
288 				options=lookup_option(common_opts, TOK_IDENT_VAL(tokens+0));
289 			if(options==NULL && (tokz->flags&TOKZ_DEFAULT_OPTION)){
290 				options=lookup_option(tokz->optstack[tokz->nest_lvl], "#default");
291 				is_default=(options!=NULL);
292 			}
293 
294 			if(options==NULL){
295 				had_error=TRUE;
296 				tokz_warn_error(tokz, tokens->line, E_TOKZ_UNKNOWN_OPTION);
297 			}else if(!is_default) {
298 				had_error=!check_args(tokz, tokens, ntokens, options->argfmt);
299 			}
300 
301 			if(had_error)
302 				break;
303 
304 			/* Found the option and arguments are ok */
305 
306 			if(options->opts!=NULL){
307 				if(t!=P_STMT_SECT){
308 					had_error=TRUE;
309 					tokz_warn_error(tokz, tokz->line, E_TOKZ_LBRACE_EXPECTED);
310 				}else if(tokz->nest_lvl==MAX_NEST-1){
311 					tokz_warn_error(tokz, tokz->line, E_TOKZ_MAX_NEST);
312 					had_error=TRUE;
313 				}else{
314 					tokz->nest_lvl++;
315 					tokz->optstack[tokz->nest_lvl]=options->opts;
316 				}
317 			}else if(t==P_STMT_SECT){
318 				had_error=TRUE;
319 				tokz_warn_error(tokz, tokz->line, E_TOKZ_SYNTAX);
320 			}
321 
322 			if(!had_error && options->fn!=NULL){
323 				had_error=!options->fn(tokz, ntokens, tokens);
324 				if(t==P_STMT_SECT && had_error)
325 					tokz->nest_lvl--;
326 			}
327 			break;
328 
329 		case P_EOF:
330 			if(tokz_popf(tokz)){
331 				break;
332 			}else if(tokz->nest_lvl>0 || errornest>0){
333 				tokz_warn_error(tokz, 0, E_TOKZ_UNEXPECTED_EOF);
334 				had_error=TRUE;
335 			}
336 			goto eof;
337 
338 		case P_BEG_SECT:
339 			had_error=TRUE;
340 			errornest++;
341 			tokz_warn_error(tokz, tokz->line, E_TOKZ_SYNTAX);
342 			break;
343 
344 		case P_END_SECT:
345 			if(tokz->nest_lvl+errornest==0){
346 				tokz_warn_error(tokz, tokz->line, E_TOKZ_SYNTAX);
347 				had_error=TRUE;
348 			}
349 
350 			if(had_error)
351 				break;
352 
353 			if(errornest!=0){
354 				errornest--;
355 			}else{
356 				had_error=!call_end_sect(tokz, tokz->optstack[tokz->nest_lvl]);
357 				tokz->nest_lvl--;
358 			}
359 
360 			if(tokz->nest_lvl<init_nest_lvl)
361 				goto eof;
362 		}
363 
364 		if(!had_error)
365 			continue;
366 
367 		if(t==P_STMT_SECT)
368 			errornest++;
369 
370 		if(!(tokz->flags&TOKZ_ERROR_TOLERANT))
371 			break;
372 	}
373 
374 eof:
375 	/* free the tokens */
376 	while(ntokens--)
377 		tok_free(&tokens[ntokens]);
378 
379 	while(tokz->nest_lvl>=init_nest_lvl){
380 		if(tokz->flags&TOKZ_ERROR_TOLERANT || !had_error)
381 			call_end_sect(tokz, tokz->optstack[tokz->nest_lvl]);
382 		else
383 			call_cancel_sect(tokz, tokz->optstack[tokz->nest_lvl]);
384 		tokz->nest_lvl--;
385 	}
386 
387 	/* Free optstack if it was alloced by this call */
388 	if(alloced_optstack){
389 		free(tokz->optstack);
390 		tokz->optstack=NULL;
391 		tokz->nest_lvl=0;
392 	}
393 
394 	if(tokz->flags&TOKZ_PARSER_INDENT_MODE)
395 		verbose_indent(init_nest_lvl);
396 
397 	return !had_error;
398 }
399 
400 
401 /* */
402 
403 
parse_config(const char * fname,const ConfOpt * options,int flags)404 bool parse_config(const char *fname, const ConfOpt *options, int flags)
405 {
406 	Tokenizer *tokz;
407 	bool ret;
408 
409 	tokz=tokz_open(fname);
410 
411 	if(tokz==NULL)
412 		return FALSE;
413 
414 	tokz->flags|=flags&~TOKZ_READ_COMMENTS;
415 
416 	ret=parse_config_tokz(tokz, options);
417 
418 	tokz_close(tokz);
419 
420 	return ret;
421 }
422 
423 
parse_config_file(FILE * file,const ConfOpt * options,int flags)424 bool parse_config_file(FILE *file, const ConfOpt *options, int flags)
425 {
426 	Tokenizer *tokz;
427 	bool ret;
428 
429 	tokz=tokz_open_file(file, NULL);
430 
431 	if(tokz==NULL)
432 		return FALSE;
433 
434 	tokz->flags|=flags&~TOKZ_READ_COMMENTS;
435 
436 	ret=parse_config_tokz(tokz, options);
437 
438 	tokz_close(tokz);
439 
440 	return ret;
441 }
442 
443 
444 /*
445  * Argument validity checking stuff
446  */
447 
448 
arg_match(Token * tok,char c,bool si)449 static int arg_match(Token *tok, char c, bool si)
450 {
451 	char c2=tok->type;
452 
453 	if(c=='.' || c=='*')
454 		return 0;
455 
456 	if(c2==c)
457 		return 0;
458 
459 	if(si){
460 		if(c2=='i' && c=='s'){
461 			TOK_SET_IDENT(tok, TOK_STRING_VAL(tok));
462 			return 0;
463 		}
464 
465 		if(c2=='s' && c=='i'){
466 			TOK_SET_STRING(tok, TOK_IDENT_VAL(tok));
467 			return 0;
468 		}
469 	}
470 
471 	if(c2=='c' && c=='l'){
472 		TOK_SET_LONG(tok, TOK_CHAR_VAL(tok));
473 		return 0;
474 	}
475 
476 	if(c2=='l' && c=='c'){
477 		TOK_SET_CHAR(tok, TOK_LONG_VAL(tok));
478 		return 0;
479 	}
480 
481 	if(c2=='l' && c=='d'){
482 		TOK_SET_DOUBLE(tok, TOK_LONG_VAL(tok));
483 		return 0;
484 	}
485 
486 	if(c=='b'){
487 		if(c2=='l'){
488 			TOK_SET_BOOL(tok, TOK_LONG_VAL(tok));
489 			return 0;
490 		}else if(c2=='i'){
491 			if(strcmp(TOK_IDENT_VAL(tok), "TRUE")==0){
492 				tok_free(tok);
493 				TOK_SET_BOOL(tok, TRUE);
494 				return 0;
495 			}else if(strcmp(TOK_IDENT_VAL(tok), "FALSE")==0){
496 				tok_free(tok);
497 				TOK_SET_BOOL(tok, FALSE);
498 				return 0;
499 			}
500 		}
501 	}
502 
503 	return E_TOKZ_INVALID_ARGUMENT;
504 }
505 
506 
check_argument(const char ** pret,Token * tok,const char * p,bool si)507 static int check_argument(const char **pret, Token *tok, const char *p,
508 						  bool si)
509 {
510 	int mode;
511 	int e=E_TOKZ_TOO_MANY_ARGS;
512 
513 again:
514 	mode=0;
515 
516 	if(*p=='*'){
517 		*pret=p;
518 		return 0;
519 	}else if(*p=='?'){
520 		mode=1;
521 		p++;
522 	}else if(*p==':'){
523 		mode=2;
524 		p++;
525 	}else if(*p=='+'){
526 		*pret=p;
527 		return arg_match(tok, *(p-1), si);
528 	}
529 
530 	while(*p!='\0'){
531 		e=arg_match(tok, *p, si);
532 		if(e==0){
533 			p++;
534 			while(mode==2 && *p==':'){
535 				if(*++p=='\0')
536 					break; /* Invalid argument format string, though... */
537 				p++;
538 			}
539 			*pret=p;
540 			return 0;
541 		}
542 
543 		if(mode==0)
544 			break;
545 
546 		p++;
547 
548 		if(mode==1)
549 			goto again;
550 
551 		/* mode==2 */
552 
553 		if(*p!=':')
554 			break;
555 		p++;
556 		e=E_TOKZ_TOO_MANY_ARGS;
557 	}
558 
559 	*pret=p;
560 	return e;
561 }
562 
563 
args_at_end(const char * p)564 static bool args_at_end(const char *p)
565 {
566 	if(p==NULL)
567 		return TRUE;
568 
569 	while(*p!='\0'){
570 		if(*p=='*' || *p=='+')
571 			p++;
572 		else if(*p=='?')
573 			p+=2;
574 		else
575 			return FALSE;
576 	}
577 
578 	return TRUE;
579 }
580 
581 
do_check_args(const Tokenizer * tokz,Token * tokens,int ntokens,const char * fmt,bool si)582 bool do_check_args(const Tokenizer *tokz, Token *tokens, int ntokens,
583 				   const char *fmt, bool si)
584 {
585 	int i;
586 	int e;
587 
588 	if(fmt==NULL){
589 		if(ntokens!=1)
590 			tokz_warn_error(tokz, tokens[0].line, E_TOKZ_TOO_MANY_ARGS);
591 		return ntokens==1;
592 	}
593 
594 	for(i=1; i<ntokens; i++){
595 		e=check_argument(&fmt, &tokens[i], fmt, si);
596 		if(e!=0){
597 			tokz_warn_error(tokz, tokens[i].line, e);
598 			return FALSE;
599 		}
600 	}
601 
602 	if(!args_at_end(fmt)){
603 		tokz_warn_error(tokz, tokens[i].line, E_TOKZ_TOO_FEW_ARGS);
604 		return FALSE;
605 	}
606 
607 	return TRUE;
608 }
609 
610 
check_args(const Tokenizer * tokz,Token * tokens,int ntokens,const char * fmt)611 bool check_args(const Tokenizer *tokz, Token *tokens, int ntokens,
612 				const char *fmt)
613 {
614 	return do_check_args(tokz, tokens, ntokens, fmt, FALSE);
615 }
616 
617 
check_args_loose(const Tokenizer * tokz,Token * tokens,int ntokens,const char * fmt)618 bool check_args_loose(const Tokenizer *tokz, Token *tokens, int ntokens,
619 				const char *fmt)
620 {
621 	return do_check_args(tokz, tokens, ntokens, fmt, TRUE);
622 }
623 
624 
625 /* */
626 
627 
try_include(Tokenizer * tokz,const char * fname)628 static bool try_include(Tokenizer *tokz, const char *fname)
629 {
630 	FILE *f;
631 
632 	f=fopen(fname, "r");
633 
634 	if(f==NULL)
635 		return FALSE;
636 
637 	if(!tokz_pushf_file(tokz, f, fname)){
638 		fclose(f);
639 		return FALSE;
640 	}
641 
642 	return TRUE;
643 }
644 
645 
try_include_dir(Tokenizer * tokz,const char * dir,int dlen,const char * file)646 static bool try_include_dir(Tokenizer *tokz, const char *dir, int dlen,
647 						const char *file)
648 {
649 	char *tmpname;
650 	bool retval;
651 
652 	tmpname=scatn(dir, dlen, file, -1);
653 
654 	if(tmpname==NULL){
655 		warn_err();
656 		return FALSE;
657 	}
658 
659 	retval=try_include(tokz, tmpname);
660 
661 	free(tmpname);
662 
663 	return retval;
664 }
665 
666 
opt_include(Tokenizer * tokz,int n,Token * toks)667 static bool opt_include(Tokenizer *tokz, int n, Token *toks)
668 {
669 	const char *fname=TOK_STRING_VAL(toks+1);
670 	const char *lastndx=NULL;
671 	bool retval, e;
672 	int i=0;
673 
674 	if(fname[0]!='/' && tokz->name!=NULL)
675 		lastndx=strrchr(tokz->name, '/');
676 
677 	if(lastndx==NULL)
678 		retval=try_include(tokz, fname);
679 	else
680 		retval=try_include_dir(tokz, tokz->name, lastndx-tokz->name+1, fname);
681 
682 	if(retval==TRUE)
683 		return TRUE;
684 
685 	e=errno;
686 
687 	if(tokz->includepaths!=NULL){
688 		while(tokz->includepaths[i]!=NULL){
689 			if(try_include_dir(tokz, tokz->includepaths[i], -1, fname))
690 				return TRUE;
691 			i++;
692 		}
693 	}
694 
695 	warn_obj(fname, "%s", strerror(e));
696 
697 	return FALSE;
698 }
699 
700 
tokz_set_includepaths(Tokenizer * tokz,char ** paths)701 extern void tokz_set_includepaths(Tokenizer *tokz, char **paths)
702 {
703 	tokz->includepaths=paths;
704 }
705 
706 
707 
708 ConfOpt libtu_dummy_confopts[]={
709 	END_CONFOPTS
710 };
711 
712 
713 
parse_config_tokz_skip_section(Tokenizer * tokz)714 bool parse_config_tokz_skip_section(Tokenizer *tokz)
715 {
716 	return parse_config_tokz(tokz, libtu_dummy_confopts);
717 }
718