]> git.decadent.org.uk Git - ion3.git/blob - libtu/parser.c
[svn-inject] Installing original source of ion3
[ion3.git] / libtu / parser.c
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 "parser.h"
14 #include "misc.h"
15 #include "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
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
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
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     
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
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
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
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
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
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
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                            
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
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
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
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
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
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
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
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
714 bool parse_config_tokz_skip_section(Tokenizer *tokz)
715 {
716     return parse_config_tokz(tokz, libtu_dummy_confopts);
717 }