1 /*
2  * nanoparser 1.0
3  * A tiny stand-alone easy-to-use parser written in C
4  * Copyright (c) 2010  Alexandre Martins <alemartf(at)gmail(dot)com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy of this
7  * software and associated documentation files (the "Software"), to deal in the Software
8  * without restriction, including without limitation the rights to use, copy, modify, merge,
9  * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
10  * to whom the Software is furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in all copies or
13  * substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
17  * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
18  * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  */
22 
23 /*
24                                     INSTRUCTIONS
25 
26 I)   Installation
27 
28         Just drop nanoparser.c and nanoparser.h into your project and you're done.
29 
30 II)  Usage
31 
32         parsetree_program_t *tree;
33 
34         tree = nanoparser_construct_tree("my text file.txt");
35         interpret(tree); // you write this
36         tree = nanoparser_deconstruct_tree(tree);
37 
38 III) "my text file.txt" example:
39 
40         // hello, this is a comment!
41         #include "config.cfg" // you may include other files too!
42 
43         resource "skybox"
44         {
45             type                TEXTURE
46             properties {
47                 file            "images/space skybox.jpg"
48                 color           32 48 64        // rgb color, r=32, g=48, b=64
49                 speed           0.5 0.3         // x-speed, y-speed
50                 dimensions {
51                     width       128
52                     height      128
53                 }
54             }
55         }
56 */
57 
58 
59 /*
60 
61 We implement a LL(1) parser.
62 
63 Context-free grammar:
64 
65 <program> ::= <statement> <program> | EMPTY
66 <statement> ::= STRING <parameter> <nl>
67 <parameter> ::= STRING <parameter> | <block> | EMPTY
68 <block> ::= <nq> '{' <nl> <program> '}'
69 <nl> ::= '\n' <nl> | '\n'
70 <nq> := '\n' | EMPTY
71 
72 where:
73 
74     STRING is:
75             a single-line double-quoted text (e.g., "Hello, world! Texts can be \"quoted\".")
76             or
77             a sequence of printable characters not in { ' ', '{', '}' } (e.g., hello_world)
78             http://en.wikipedia.org/wiki/ASCII
79 
80     EMPTY is a zero-length symbol
81 
82 pre-processing phase:
83 
84     1. clears all comments
85     2. processes all #include directives
86 
87 */
88 
89 #include <stdio.h>
90 #include <stdlib.h>
91 #include <stdarg.h>
92 #include <string.h>
93 #include <ctype.h>
94 #include "nanoparser.h"
95 
96 #ifdef TRUE
97 #undef TRUE
98 #endif
99 
100 #ifdef FALSE
101 #undef FALSE
102 #endif
103 
104 #define TRUE -1
105 #define FALSE 0
106 /*#define NANOPARSER_DEBUG_MODE*/
107 
108 #define GENERATE_INTERFACE_OF_EXPANDABLE_ARRAY(T)                                               \
109                                                                                                 \
110     typedef struct expandable_array_##T {                                                       \
111         T *_data; int _size; int _capacity;                                                     \
112     } expandable_array_##T ;                                                                    \
113                                                                                                 \
114     expandable_array_##T *expandable_array_##T##_new();                                         \
115     expandable_array_##T *expandable_array_##T##_delete(expandable_array_##T *array);           \
116     void expandable_array_##T##_push_back(expandable_array_##T *array, T element);              \
117     void expandable_array_##T##_pop_back(expandable_array_##T *array);                          \
118     T *expandable_array_##T##_at(expandable_array_##T *array, int index);                       \
119     int expandable_array_##T##_size(expandable_array_##T *array)
120 
121 #define GENERATE_IMPLEMENTATION_OF_EXPANDABLE_ARRAY(T)                                          \
122                                                                                                 \
123     expandable_array_##T *expandable_array_##T##_new()                                          \
124     {                                                                                           \
125         expandable_array_##T *a = malloc(sizeof *a);                                            \
126         a->_size = 0; a->_capacity = 8; a->_data = malloc_x(a->_capacity * sizeof(T));          \
127         return a;                                                                               \
128     }                                                                                           \
129                                                                                                 \
130     expandable_array_##T *expandable_array_##T##_delete(expandable_array_##T *array)            \
131     {                                                                                           \
132         if(array) { free(array->_data); free(array); }                                          \
133         return NULL;                                                                            \
134     }                                                                                           \
135                                                                                                 \
136     void expandable_array_##T##_push_back(expandable_array_##T *array, T element)               \
137     {                                                                                           \
138         if(array->_size >= array->_capacity) {                                                  \
139             array->_capacity *= 2;                                                              \
140             array->_data = realloc_x(array->_data, array->_capacity * sizeof(T));               \
141         }                                                                                       \
142         array->_data[ array->_size++ ] = element;                                               \
143     }                                                                                           \
144                                                                                                 \
145     void expandable_array_##T##_pop_back(expandable_array_##T *array)                           \
146     {                                                                                           \
147         if(array->_size > 0) array->_size--;                                                    \
148     }                                                                                           \
149                                                                                                 \
150     T *expandable_array_##T##_at(expandable_array_##T *array, int index)                        \
151     {                                                                                           \
152         int i = index;                                                                          \
153         if(i < 0) { i = 0; } else if(i >= array->_size) { i = array->_size - 1; }               \
154         return &(array->_data[i]);                                                              \
155     }                                                                                           \
156                                                                                                 \
157     int expandable_array_##T##_size(expandable_array_##T *array)                                \
158     {                                                                                           \
159         return array->_size;                                                                    \
160     }                                                                                           \
161                                                                                                 \
162     expandable_array_##T *expandable_array_##T##_new()
163 
164 #ifdef __cplusplus
165 extern "C" {
166 #endif
167 
168 
169 
170 /* private structures */
171 
172 
173 /* a program is a list of statements */
174 struct parsetree_program_t {
175     parsetree_statement_t *statement;
176     parsetree_program_t *next;
177 };
178 
179 /* a statement is a line containing an identifier (i.e., a string) followed by a parameter */
180 struct parsetree_statement_t {
181     char *string;
182     parsetree_parameter_t *parameter;
183 };
184 
185 /* a parameter is either:
186    i)   another program;
187    ii)  a string followed by another parameter */
188 struct parsetree_parameter_t {
189     enum { VALUE, PROGRAM } type;
190     union {
191         struct {
192             char *string;
193             parsetree_parameter_t *next;
194         } value;
195         parsetree_program_t *program;
196     } data;
197 };
198 
199 
200 /* utilities */
201 static void (*error_fun)(const char*) = NULL;
202 static void (*warning_fun)(const char*) = NULL;
203 static void error(const char *fmt, ...); /* fatal error */
204 static void warning(const char *fmt, ...); /* warning */
205 static char* dirpath(const char *filepath); /* dirpath("f/folder/file.txt") = "f/folder/" */
206 static void* malloc_x(size_t bytes); /* our version of malloc */
207 static void* realloc_x(void *ptr, size_t bytes); /* our version of realloc */
208 static char* str_dup(const char *s); /* our version of strdup: duplicates s */
209 static char* r_trim(char *s); /* r_trim */
210 
211 
212 
213 /* parse tree */
214 static parsetree_parameter_t* parsetree_parameter_new_value(const char *str, parsetree_parameter_t *nextparam);
215 static parsetree_parameter_t* parsetree_parameter_new_program(parsetree_program_t *prog);
216 static parsetree_parameter_t* parsetree_parameter_delete(parsetree_parameter_t* param);
217 static void parsetree_parameter_show(parsetree_parameter_t* param);
218 
219 static parsetree_statement_t* parsetree_statement_new(const char *str, parsetree_parameter_t *parameter);
220 static parsetree_statement_t* parsetree_statement_delete(parsetree_statement_t* stmt);
221 static void parsetree_statement_show(parsetree_statement_t* stmt);
222 
223 static parsetree_program_t* parsetree_program_new(parsetree_statement_t* stmt, parsetree_program_t* nextprog);
224 static parsetree_program_t* parsetree_program_delete(parsetree_program_t* prog);
225 static void parsetree_program_show(parsetree_program_t* prog);
226 
227 
228 
229 /* virtual file (in-memory) */
230 GENERATE_INTERFACE_OF_EXPANDABLE_ARRAY(int);
231 GENERATE_IMPLEMENTATION_OF_EXPANDABLE_ARRAY(int);
232 static char* vfile_name; /* filename */
233 static expandable_array_int* vfile_contents; /* file contents */
234 static int vfile_ptr; /* current file pointer */
235 
236 static void vfile_create(const char *name); /* creates the virtual file */
237 static void vfile_destroy(); /* destroys the virtual file */
238 static int vfile_getc(); /* getchar */
239 static int vfile_ungetc(int c); /* ungetchar */
240 static int vfile_putc(int c); /* putchar */
241 static void vfile_rewind(); /* rewind */
242 
243 
244 
245 /* preprocessor */
246 typedef char* pchar;
247 GENERATE_INTERFACE_OF_EXPANDABLE_ARRAY(pchar);
248 GENERATE_IMPLEMENTATION_OF_EXPANDABLE_ARRAY(pchar);
249 static expandable_array_pchar* preprocessor_include_table; /* avoids infinite recursive inclusions */
250 static int preprocessor_line; /* current line number */
251 
252 static void preprocessor_init();
253 static void preprocessor_release();
254 static void preprocessor_run(FILE *in, int depth); /* runs the preprocessor */
255 static void preprocessor_show(); /* shows the pre-processed file */
256 static void preprocessor_add_to_include_table(const char *filepath);
257 static int preprocessor_has_file_been_included(const char *filepath);
258 
259 
260 
261 /* error detection: this is used to detect WHERE errors are located (after preprocessing) */
262 typedef struct {
263     /* filename */
264     char *filename;
265 
266     /* file [filename] starts at line [vline_start_line] of the virtual preprocessed file */
267     int vfile_start_line;
268 
269     /* vfile_line_offset: used if there's another file included within filename (otherwise it's 0) */
270     int vfile_line_offset;
271 } errorcontext;
272 GENERATE_INTERFACE_OF_EXPANDABLE_ARRAY(errorcontext);
273 GENERATE_IMPLEMENTATION_OF_EXPANDABLE_ARRAY(errorcontext);
274 static expandable_array_errorcontext* errorcontext_table;
275 
276 static void errorcontext_init(); /* initializes the error context module */
277 static void errorcontext_release(); /* releases the erro context module */
278 static void errorcontext_add_to_table(const char *filename, int vfile_start_line, int vfile_line_offset); /* adds a new error context to its internal table */
279 static int errorcontext_detect_file_line(int vfile_line); /* provide a line number of the virtual file, and it will return you the line number of the real file at that place */
280 static const char* errorcontext_detect_file_name(int vfile_line); /* provide a line number of the virtual file, and it will return you the name of the real file at that place */
281 static errorcontext* errorcontext_find(int idx, int vfile_line); /* internal use only */
282 
283 
284 /* lexical analyzer */
285 #define SYMBOL_MAXLENGTH        512
286 
287 typedef enum {
288     SYM_EOF,
289     SYM_NEWLINE,
290     SYM_STRING,
291     SYM_BEGINBLOCK,
292     SYM_ENDBLOCK
293 } symbol_t;
294 
295 static int line; /* current line number (after preprocessing phase) */
296 static symbol_t sym, oldsym; /* current/old token */
297 static char symdata[SYMBOL_MAXLENGTH+1], oldsymdata[SYMBOL_MAXLENGTH+1]; /* current/old token textual data */
298 
299 static void getsym(); /* read the next token */
300 static void ungetsym(); /* put the last read token back into the stream */
301 
302 
303 
304 /* syntatic analyzer */
305 static parsetree_program_t* parse(); /* main parsing routine */
306 static int accept(symbol_t s); /* reads the next token and returns true iff s is found */
307 static int expect(symbol_t s); /* throws an error if s doesn't get accepted */
308 
309 
310 
311 /* grammar rules */
312 static parsetree_program_t* program();
313 static parsetree_statement_t* statement();
314 static parsetree_parameter_t* parameter();
315 static parsetree_program_t* block();
316 static void nl();
317 static void nq();
318 
319 
320 /* tree traversal */
321 static int traversal_adapter(const parsetree_statement_t *stmt, void *eval_fun);
322 
323 
324 
325 
326 
327 /* ---------------------------------------------
328  * public methods of the parser
329  * ---------------------------------------------- */
330 
nanoparser_construct_tree(const char * filepath)331 parsetree_program_t* nanoparser_construct_tree(const char *filepath)
332 {
333     FILE *fp;
334     parsetree_program_t *prog;
335 
336     fp = fopen(filepath, "r");
337     if(fp != NULL) {
338         /* creates the temporary virtual file */
339         vfile_create(filepath);
340 
341         /* initializes the error context module (used for improved error detection) */
342         errorcontext_init();
343         errorcontext_add_to_table(filepath, 1, 0);
344 
345         /* calls the preprocessor */
346         preprocessor_init();
347         preprocessor_add_to_include_table(filepath); /* you can't #include yourself */
348         preprocessor_run(fp, 0);
349         preprocessor_release();
350 
351         /* calls the parser */
352         prog = parse();
353 
354         /* releases the error context module */
355         errorcontext_release();
356 
357         /* destroys the temporary virtual file */
358         vfile_destroy();
359 
360         /* done! */
361         fclose(fp);
362     }
363     else {
364         prog = NULL;
365         error("Couldn't open file \"%s\" for reading.", filepath);
366     }
367 
368     return prog;
369 }
370 
nanoparser_deconstruct_tree(parsetree_program_t * tree)371 parsetree_program_t* nanoparser_deconstruct_tree(parsetree_program_t *tree)
372 {
373     tree = parsetree_program_delete(tree);
374     return tree;
375 }
376 
nanoparser_set_error_function(void (* fun)(const char *))377 void nanoparser_set_error_function(void (*fun)(const char*))
378 {
379     error_fun = fun;
380 }
381 
nanoparser_set_warning_function(void (* fun)(const char *))382 void nanoparser_set_warning_function(void (*fun)(const char*))
383 {
384     warning_fun = fun;
385 }
386 
387 
388 /* ---------------------------------------------
389  * lexical analysis
390  * ---------------------------------------------- */
391 
392 /* this is the lexer */
getsym()393 void getsym()
394 {
395     int i = 0;
396     unsigned int c;
397     char *p = symdata;
398 
399     /* create a backup */
400     oldsym = sym;
401     strcpy(oldsymdata, symdata);
402 
403     /* skip white spaces */
404     do {
405         c = vfile_getc();
406     } while(c != '\n' && isspace(c));
407 
408     /* deciding which symbol comes next */
409     if(c == EOF) {
410         sym = SYM_EOF;
411         *(p++) = (char)c;
412     }
413     else if(c == '\n') {
414         sym = SYM_NEWLINE;
415         *(p++) = (char)c;
416         ++line;
417     }
418     else if(c == '{') {
419         sym = SYM_BEGINBLOCK;
420         *(p++) = (char)c;
421     }
422     else if(c == '}') {
423         sym = SYM_ENDBLOCK;
424         *(p++) = (char)c;
425     }
426     else if(c >= 0x20) {
427         sym = SYM_STRING;
428         if(c != '"') {
429             /* non-quoted string */
430             while(c >= 0x20 && !isspace(c) && c != '{' && c != '}' && ++i <= SYMBOL_MAXLENGTH) { /* printable character */
431                 *(p++) = (char)c;
432                 c = vfile_getc();
433             }
434             vfile_ungetc(c);
435         }
436         else {
437             /* double-quoted string */
438             c = vfile_getc(); /* discard '"' */
439             while(c >= 0x20 && c != '"' && ++i <= SYMBOL_MAXLENGTH) {
440                 if(c == '\n') {
441                     error(
442                         "Unexpected end of string in \"%s\" on line %d.",
443                         errorcontext_detect_file_name(line),
444                         errorcontext_detect_file_line(line)
445                     );
446                 }
447                 else if(c == '\\') {
448                     int h = vfile_getc();
449                     switch(h) {
450                         case '"':  c = '"';  break;
451                         case 'n':  c = '\n'; break;
452                         case 't':  c = '\t'; break;
453                         case '\\': c = '\\'; break;
454                         default:
455                             error(
456                                 "Invalid character '\\%c' in \"%s\" on line %d. Did you mean '\\\\'?",
457                                 h,
458                                 errorcontext_detect_file_name(line),
459                                 errorcontext_detect_file_line(line)
460                             );
461                             break;
462                     }
463                 }
464 
465                 *(p++) = (char)c;
466                 c = vfile_getc();
467             }
468 
469             if(c != '"') /* discard '"' */
470                 vfile_ungetc(c);
471         }
472     }
473     else {
474         error(
475             "Lexical error in \"%s\" on line %d: unknown symbol \"%c\" (%d).",
476             errorcontext_detect_file_name(line),
477             errorcontext_detect_file_line(line),
478             c,
479             c
480         );
481     }
482 
483     *p = 0;
484 }
485 
ungetsym()486 void ungetsym()
487 {
488     char *str = symdata;
489     int i;
490 
491     /* putting the symbol back into the stream */
492     vfile_ungetc(' ');
493     for(i=strlen(str)-1; i>=0; i--) {
494         vfile_ungetc((int)str[i]);
495         if(str[i] == '\n')
496             --line;
497     }
498 
499     /* restoring the backup */
500     strcpy(symdata, oldsymdata);
501     sym = oldsym;
502 }
503 
accept(symbol_t s)504 int accept(symbol_t s)
505 {
506     if(sym == s) {
507         getsym();
508         return TRUE;
509     }
510     else
511         return FALSE;
512 }
513 
expect(symbol_t s)514 int expect(symbol_t s)
515 {
516     if(!accept(s)) {
517         error(
518             "Syntax error in \"%s\" on line %d: unexpected symbol \"%s\".",
519             errorcontext_detect_file_name(line),
520             errorcontext_detect_file_line(line),
521             symdata
522         );
523         return FALSE;
524     }
525     else
526         return TRUE;
527 }
528 
529 
530 /* ---------------------------------------------
531  * syntatic analysis: parser
532  * ---------------------------------------------- */
533 
parse()534 parsetree_program_t* parse()
535 {
536     parsetree_program_t *prog;
537 
538     line = 1;
539     getsym(); /* reads the first symbol */
540     while(accept(SYM_NEWLINE)); /* skips newlines */
541     prog = program(); /* generates the syntatic tree */
542     expect(SYM_EOF); /* expects an EOF character */
543 
544     return prog;
545 }
546 
547 
548 
549 
550 
551 /* ---------------------------------------------
552  * syntax analysis: grammar rules
553  * ---------------------------------------------- */
554 
program()555 parsetree_program_t* program()
556 {
557     parsetree_program_t *prog = NULL;
558 
559     if(sym != SYM_EOF && sym != SYM_ENDBLOCK) {
560         parsetree_statement_t *stmt = statement();
561         prog = parsetree_program_new(
562             stmt,
563             program()
564         );
565     }
566     else
567         ; /* empty */
568 
569     return prog;
570 }
571 
statement()572 parsetree_statement_t* statement()
573 {
574     parsetree_statement_t *stmt = NULL;
575     char *str = str_dup(symdata);
576 
577     expect(SYM_STRING);
578     stmt = parsetree_statement_new(
579         str,
580         parameter()
581     );
582     if(sym != SYM_EOF)
583         nl();
584 
585     free(str);
586     return stmt;
587 }
588 
parameter()589 parsetree_parameter_t* parameter()
590 {
591     parsetree_parameter_t *param = NULL;
592 
593     if(sym == SYM_STRING) {
594         char *str = str_dup(symdata);
595         accept(SYM_STRING);
596         param = parsetree_parameter_new_value(
597             str,
598             parameter()
599         );
600         free(str);
601     }
602     else if(sym == SYM_BEGINBLOCK) {
603         param = parsetree_parameter_new_program(
604             block()
605         );
606     }
607     else if(sym == SYM_NEWLINE) {
608         /* lookahead: do we have a block? */
609         int blk;
610 
611         getsym();
612         blk = (sym == SYM_BEGINBLOCK);
613         ungetsym();
614 
615         if(blk) {
616             param = parsetree_parameter_new_program(
617                 block()
618             );
619         }
620     }
621     else
622         ; /* empty */
623 
624     return param;
625 }
626 
block()627 parsetree_program_t* block()
628 {
629     parsetree_program_t *prog = NULL;
630 
631     nq();
632     expect(SYM_BEGINBLOCK);
633     nl();
634     prog = program();
635     expect(SYM_ENDBLOCK);
636 
637     return prog;
638 }
639 
nq()640 void nq()
641 {
642     accept(SYM_NEWLINE);
643 }
644 
nl()645 void nl()
646 {
647     expect(SYM_NEWLINE);
648     while(accept(SYM_NEWLINE));
649 }
650 
651 
652 
653 
654 /* ---------------------------------------------
655  * syntax analysis: parse tree manipulation
656  * ---------------------------------------------- */
657 
parsetree_parameter_new_value(const char * str,parsetree_parameter_t * nextparam)658 parsetree_parameter_t* parsetree_parameter_new_value(const char *str, parsetree_parameter_t *nextparam)
659 {
660     parsetree_parameter_t *p = malloc_x(sizeof *p);
661     p->type = VALUE;
662     p->data.value.string = str_dup(str);
663     p->data.value.next = nextparam;
664     return p;
665 }
666 
parsetree_parameter_new_program(parsetree_program_t * prog)667 parsetree_parameter_t* parsetree_parameter_new_program(parsetree_program_t *prog)
668 {
669     parsetree_parameter_t *p = malloc_x(sizeof *p);
670     p->type = PROGRAM;
671     p->data.program = prog;
672     return p;
673 }
674 
parsetree_parameter_delete(parsetree_parameter_t * param)675 parsetree_parameter_t* parsetree_parameter_delete(parsetree_parameter_t* param)
676 {
677     if(param != NULL) {
678         if(param->type == VALUE) {
679             free(param->data.value.string);
680             param->data.value.string = NULL;
681             param->data.value.next = parsetree_parameter_delete(param->data.value.next);
682         }
683         else
684             param->data.program = parsetree_program_delete(param->data.program);
685 
686         free(param);
687     }
688 
689     return NULL;
690 }
691 
parsetree_parameter_show(parsetree_parameter_t * param)692 void parsetree_parameter_show(parsetree_parameter_t* param)
693 {
694     printf("[ ");
695     if(param != NULL) {
696         if(param->type == PROGRAM) {
697             printf("\n");
698             parsetree_program_show(param->data.program);
699             printf("\n");
700         }
701         else {
702             printf("%s ", param->data.value.string);
703             parsetree_parameter_show(param->data.value.next);
704         }
705     }
706     printf(" ] ");
707 }
708 
parsetree_statement_new(const char * str,parsetree_parameter_t * parameter)709 parsetree_statement_t* parsetree_statement_new(const char *str, parsetree_parameter_t *parameter)
710 {
711     parsetree_statement_t *p = malloc_x(sizeof *p);
712     p->string = str_dup(str);
713     p->parameter = parameter;
714     return p;
715 }
716 
parsetree_statement_delete(parsetree_statement_t * stmt)717 parsetree_statement_t* parsetree_statement_delete(parsetree_statement_t* stmt)
718 {
719     if(stmt != NULL) {
720         free(stmt->string);
721         stmt->string = NULL;
722         stmt->parameter = parsetree_parameter_delete(stmt->parameter);
723         free(stmt);
724     }
725 
726     return NULL;
727 }
728 
parsetree_statement_show(parsetree_statement_t * stmt)729 void parsetree_statement_show(parsetree_statement_t* stmt)
730 {
731     if(stmt != NULL) {
732         printf("%s := ", stmt->string);
733         parsetree_parameter_show(stmt->parameter);
734         printf("\n");
735     }
736 }
737 
parsetree_program_new(parsetree_statement_t * stmt,parsetree_program_t * nextprog)738 parsetree_program_t* parsetree_program_new(parsetree_statement_t *stmt, parsetree_program_t *nextprog)
739 {
740     parsetree_program_t *p = malloc_x(sizeof *p);
741     p->statement = stmt;
742     p->next = nextprog;
743     return p;
744 }
745 
parsetree_program_delete(parsetree_program_t * prog)746 parsetree_program_t* parsetree_program_delete(parsetree_program_t *prog)
747 {
748     if(prog != NULL) {
749         prog->statement = parsetree_statement_delete(prog->statement);
750         prog->next = parsetree_program_delete(prog->next);
751         free(prog);
752     }
753 
754     return NULL;
755 }
756 
parsetree_program_show(parsetree_program_t * prog)757 void parsetree_program_show(parsetree_program_t* prog)
758 {
759     if(prog != NULL) {
760         parsetree_statement_show(prog->statement);
761         parsetree_program_show(prog->next);
762     }
763 }
764 
765 
766 
767 /* ---------------------------------------------
768  * virtual files
769  * ---------------------------------------------- */
770 
vfile_create(const char * name)771 void vfile_create(const char *name)
772 {
773     vfile_ptr = 0;
774     vfile_name = str_dup(name);
775     vfile_contents = expandable_array_int_new();
776 }
777 
vfile_destroy()778 void vfile_destroy()
779 {
780     vfile_contents = expandable_array_int_delete(vfile_contents);
781     free(vfile_name);
782     vfile_name = NULL;
783     vfile_ptr = 0;
784 }
785 
vfile_getc()786 int vfile_getc()
787 {
788     if(vfile_ptr < expandable_array_int_size(vfile_contents)) {
789         int *ptr = expandable_array_int_at(vfile_contents, vfile_ptr++);
790         return *ptr;
791     }
792     else
793         return EOF;
794 }
795 
vfile_ungetc(int c)796 int vfile_ungetc(int c)
797 {
798     if(vfile_ptr > 0 && c != EOF) {
799         int *ptr = expandable_array_int_at(vfile_contents, --vfile_ptr);
800         *ptr = c;
801         return *ptr;
802     }
803     else
804         return EOF;
805 }
806 
vfile_putc(int c)807 int vfile_putc(int c)
808 {
809     int size = expandable_array_int_size(vfile_contents);
810 
811     if(vfile_ptr < size) {
812         int *ptr = expandable_array_int_at(vfile_contents, vfile_ptr++);
813         *ptr = c;
814     }
815     else {
816         expandable_array_int_push_back(vfile_contents, c);
817         vfile_ptr = size+1;
818     }
819 
820     return c;
821 }
822 
vfile_rewind()823 void vfile_rewind()
824 {
825     vfile_ptr = 0;
826 }
827 
828 
829 
830 
831 /* ---------------------------------------------
832  * preprocessor
833  * ---------------------------------------------- */
834 
preprocessor_init()835 void preprocessor_init()
836 {
837     preprocessor_line = 1;
838     preprocessor_include_table = expandable_array_pchar_new();
839 }
840 
preprocessor_release()841 void preprocessor_release()
842 {
843     int i, len = expandable_array_pchar_size(preprocessor_include_table);
844 
845     for(i=0; i<len; i++) {
846         char **p = expandable_array_pchar_at(preprocessor_include_table, i);
847         free(*p);
848         *p = NULL;
849     }
850 
851     preprocessor_include_table = expandable_array_pchar_delete(preprocessor_include_table);
852     preprocessor_line = 1;
853     vfile_rewind();
854 }
855 
preprocessor_show()856 void preprocessor_show()
857 {
858 #ifdef NANOPARSER_DEBUG_MODE
859     int c;
860 
861     vfile_rewind();
862     while(EOF != (c=vfile_getc()))
863         putchar(c);
864     vfile_rewind();
865 #endif
866 }
867 
preprocessor_run(FILE * in,int depth)868 void preprocessor_run(FILE *in, int depth)
869 {
870     int c;
871     int line_start = TRUE;
872 
873     while(EOF != (c = fgetc(in))) {
874         /* do nothing with double-quoted strings */
875         if(c == '"') {
876             int old = c;
877             vfile_putc(c);
878             c = fgetc(in);
879             while(((c != '"') || (old == '\\' && c == '"')) && c != EOF && c != '\n') {
880                 vfile_putc(c);
881                 old = c;
882                 c = fgetc(in);
883             }
884         }
885 
886         /* ignore comments */
887         if(c == '/') {
888             int h = fgetc(in);
889             if(h == '/') {
890                 do {
891                     c = fgetc(in);
892                 } while(c != '\n' && c != EOF);
893             }
894             else
895                 ungetc(h, in);
896         }
897 
898         /* preprocessor directives */
899         if(c == '#' && line_start) {
900             char key[1+512]="", value[1+512]="";
901             int key_len = 0, value_len = 0;
902             int quot = FALSE;
903             char *p;
904 
905             /* read key */
906             p = key;
907             do {
908                 *(p++) = c;
909                 c = fgetc(in);
910             } while(!isspace(c) && c != '\n' && c != EOF && ++key_len < 512);
911             *p = 0;
912 
913             /* read value */
914             p = value;
915             while(c != '\n' && isspace(c)) /* skip spaces */
916                 c = fgetc(in);
917             while(c != '\n' && c != EOF && value_len++ < 512) {
918                 if(c == '/' && !quot) {
919                     int h = fgetc(in);
920                     if(h == '/')
921                         break;
922                     else
923                         ungetc(h, in);
924                 }
925                 if(c != '"')
926                     *(p++) = c;
927                 else
928                     quot = !quot;
929                 c = fgetc(in);
930             }
931             *p = 0;
932             r_trim(value);
933 
934             /* processing... */
935             if(strcmp(key, "#include") == 0) {
936                 char *dir = dirpath(vfile_name);
937                 char *fullpath = malloc_x((1+strlen(value)+strlen(dir)) * sizeof(*fullpath));
938 
939                 strcpy(fullpath, dir);
940                 strcat(fullpath, value);
941 
942                 if(!preprocessor_has_file_been_included(fullpath)) {
943                     FILE *fp = fopen(fullpath, "r");
944                     preprocessor_add_to_include_table(fullpath);
945                     if(fp != NULL) {
946                         char *old_vfile_name = vfile_name;
947                         const char *me = errorcontext_detect_file_name(preprocessor_line);
948                         int mel = errorcontext_detect_file_line(preprocessor_line);
949                         errorcontext_add_to_table(fullpath, preprocessor_line, 0);
950 
951                         vfile_name = str_dup(fullpath);
952                         preprocessor_run(fp, depth+1);
953                         free(vfile_name);
954                         vfile_name = old_vfile_name;
955 
956                         errorcontext_add_to_table(me, preprocessor_line, mel);
957                         fclose(fp);
958                     }
959                     else {
960                         error(
961                             "Preprocessor error in \"%s\" on line %d: couldn't include file \"%s\".",
962                             errorcontext_detect_file_name(preprocessor_line),
963                             errorcontext_detect_file_line(preprocessor_line),
964                             fullpath
965                         );
966                     }
967 
968                     free(fullpath);
969                     free(dir);
970 
971                     line_start = TRUE;
972                     continue;
973                 }
974                 else {
975                     error(
976                         "Preprocessor error in \"%s\" on line %d: file \"%s\" has already been included.",
977                         errorcontext_detect_file_name(preprocessor_line),
978                         errorcontext_detect_file_line(preprocessor_line),
979                         fullpath
980                     );
981                 }
982             }
983             else {
984                 /* we'll consider unknown pre-processor commands as being comments */
985                 warning(
986                     "Preprocessor error in \"%s\" on line %d: unknown command \"%s %s\".",
987                     errorcontext_detect_file_name(preprocessor_line),
988                     errorcontext_detect_file_line(preprocessor_line),
989                     key,
990                     value
991                 );
992             }
993 
994         }
995 
996         /* new line... */
997         if(c == '\n') {
998             line_start = TRUE;
999             preprocessor_line++;
1000         }
1001         else if(!isspace(c))
1002             line_start = FALSE;
1003 
1004         /* accept this character */
1005         if(c != EOF)
1006             vfile_putc(c);
1007         else if(feof(in))
1008             vfile_putc(c);
1009         else if(ferror(in))
1010             error("Error reading from stream '%s'", vfile_name);
1011     }
1012 
1013     if(depth == 0) {
1014         /* rewinds the virtual file */
1015         vfile_rewind();
1016 
1017         /* debug information */
1018         preprocessor_show();
1019     }
1020 }
1021 
preprocessor_has_file_been_included(const char * filename)1022 int preprocessor_has_file_been_included(const char *filename)
1023 {
1024     int i, len = expandable_array_pchar_size(preprocessor_include_table);
1025     char *file;
1026 
1027     for(i=0; i<len; i++) {
1028         file = *(expandable_array_pchar_at(preprocessor_include_table, i));
1029         if(strcmp(file, filename) == 0)
1030             return TRUE;
1031     }
1032 
1033     return FALSE;
1034 }
1035 
preprocessor_add_to_include_table(const char * filepath)1036 void preprocessor_add_to_include_table(const char *filepath)
1037 {
1038     expandable_array_pchar_push_back(preprocessor_include_table, (pchar)str_dup(filepath));
1039 }
1040 
1041 
1042 
1043 /* ---------------------------------------------
1044  * improved error detection
1045  * ---------------------------------------------- */
1046 
errorcontext_init()1047 void errorcontext_init()
1048 {
1049     errorcontext_table = expandable_array_errorcontext_new();
1050 }
1051 
errorcontext_release()1052 void errorcontext_release()
1053 {
1054     int i, size = expandable_array_errorcontext_size(errorcontext_table);
1055 
1056     for(i=0; i<size; i++)
1057         free( expandable_array_errorcontext_at(errorcontext_table, i)->filename );
1058 
1059     errorcontext_table = expandable_array_errorcontext_delete(errorcontext_table);
1060 }
1061 
errorcontext_add_to_table(const char * filename,int vfile_start_line,int vfile_line_offset)1062 void errorcontext_add_to_table(const char *filename, int vfile_start_line, int vfile_line_offset)
1063 {
1064     errorcontext ctx;
1065     ctx.filename = str_dup(filename);
1066     ctx.vfile_start_line = vfile_start_line;
1067     ctx.vfile_line_offset = vfile_line_offset;
1068     expandable_array_errorcontext_push_back(errorcontext_table, ctx);
1069 }
1070 
errorcontext_find(int idx,int vfile_line)1071 errorcontext* errorcontext_find(int idx, int vfile_line)
1072 {
1073     int size = expandable_array_errorcontext_size(errorcontext_table);
1074 
1075     if(idx < 0)
1076         return expandable_array_errorcontext_at(errorcontext_table, 0);
1077     else if(idx >= size)
1078         return expandable_array_errorcontext_at(errorcontext_table, size-1);
1079     else if(vfile_line < expandable_array_errorcontext_at(errorcontext_table, idx)->vfile_start_line)
1080         return expandable_array_errorcontext_at(errorcontext_table, idx>0 ? idx-1 : 0);
1081     else
1082         return errorcontext_find(idx+1, vfile_line);
1083 }
1084 
errorcontext_detect_file_line(int vfile_line)1085 int errorcontext_detect_file_line(int vfile_line)
1086 {
1087     errorcontext *c = errorcontext_find(0, vfile_line);
1088     return 1 + vfile_line - c->vfile_start_line + c->vfile_line_offset;
1089 }
1090 
errorcontext_detect_file_name(int vfile_line)1091 const char* errorcontext_detect_file_name(int vfile_line)
1092 {
1093     errorcontext *c = errorcontext_find(0, vfile_line);
1094     return c->filename;
1095 }
1096 
1097 
1098 
1099 /* ---------------------------------------------
1100  * tree traversal
1101  * ---------------------------------------------- */
nanoparser_traverse_program(const parsetree_program_t * program,int (* eval)(const parsetree_statement_t *))1102 void nanoparser_traverse_program(const parsetree_program_t *program, int (*eval)(const parsetree_statement_t*))
1103 {
1104     nanoparser_traverse_program_ex(program, (void*)eval, traversal_adapter);
1105 }
1106 
nanoparser_traverse_program_ex(const parsetree_program_t * program,void * some_user_data,int (* eval)(const parsetree_statement_t *,void *))1107 void nanoparser_traverse_program_ex(const parsetree_program_t *program, void *some_user_data, int (*eval)(const parsetree_statement_t*,void*))
1108 {
1109     const parsetree_program_t *p;
1110 
1111     for(p = program; p != NULL; p = p->next) {
1112         if(eval(p->statement, some_user_data) != 0)
1113             break;
1114     }
1115 }
1116 
traversal_adapter(const parsetree_statement_t * stmt,void * eval_fun)1117 int traversal_adapter(const parsetree_statement_t *stmt, void *eval_fun)
1118 {
1119     int (*eval)(const parsetree_statement_t*) = (int (*)(const parsetree_statement_t*))eval_fun;
1120     return eval(stmt);
1121 }
1122 
1123 
1124 
1125 /* ---------------------------------------------
1126  * statement handling
1127  * ---------------------------------------------- */
1128 
nanoparser_get_identifier(const parsetree_statement_t * stmt)1129 const char* nanoparser_get_identifier(const parsetree_statement_t *stmt)
1130 {
1131     return stmt->string;
1132 }
1133 
nanoparser_get_parameter_list(const parsetree_statement_t * stmt)1134 const parsetree_parameter_t* nanoparser_get_parameter_list(const parsetree_statement_t *stmt)
1135 {
1136     return stmt->parameter;
1137 }
1138 
1139 
1140 /* ---------------------------------------------
1141  * data retrieval
1142  * ---------------------------------------------- */
1143 
nanoparser_get_number_of_parameters(const parsetree_parameter_t * param_list)1144 int nanoparser_get_number_of_parameters(const parsetree_parameter_t *param_list)
1145 {
1146     if(param_list != NULL) {
1147         if(param_list->type == VALUE)
1148             return 1 + nanoparser_get_number_of_parameters(param_list->data.value.next);
1149         else
1150             return 1;
1151     }
1152     else
1153         return 0;
1154 }
1155 
nanoparser_get_nth_parameter(const parsetree_parameter_t * param_list,int n)1156 const parsetree_parameter_t* nanoparser_get_nth_parameter(const parsetree_parameter_t *param_list, int n)
1157 {
1158     if(param_list != NULL && n >= 1) {
1159         if(n == 1)
1160             return param_list;
1161         else if(param_list->type == VALUE)
1162             return nanoparser_get_nth_parameter(param_list->data.value.next, n-1);
1163     }
1164 
1165     return NULL;
1166 }
1167 
nanoparser_expect_string(const parsetree_parameter_t * param,const char * error_message)1168 void nanoparser_expect_string(const parsetree_parameter_t *param, const char *error_message)
1169 {
1170     if(param == NULL || (param != NULL && param->type != VALUE))
1171         error("%s", error_message);
1172 }
1173 
nanoparser_expect_program(const parsetree_parameter_t * param,const char * error_message)1174 void nanoparser_expect_program(const parsetree_parameter_t *param, const char *error_message)
1175 {
1176     if(param == NULL || (param != NULL && param->type != PROGRAM))
1177         error("%s", error_message);
1178 }
1179 
nanoparser_get_string(const parsetree_parameter_t * param)1180 const char* nanoparser_get_string(const parsetree_parameter_t *param)
1181 {
1182     if(param != NULL && param->type == VALUE)
1183         return param->data.value.string;
1184     else
1185         return "null";
1186 }
1187 
nanoparser_get_program(const parsetree_parameter_t * param)1188 const parsetree_program_t* nanoparser_get_program(const parsetree_parameter_t *param)
1189 {
1190     if(param != NULL && param->type == PROGRAM)
1191         return param->data.program;
1192     else
1193         return NULL;
1194 }
1195 
1196 
1197 /* ---------------------------------------------
1198  * operations
1199  * --------------------------------------------- */
nanoparser_append_program(parsetree_program_t * dest,parsetree_program_t * src)1200 parsetree_program_t* nanoparser_append_program(parsetree_program_t *dest, parsetree_program_t *src)
1201 {
1202     if(dest != NULL) {
1203         parsetree_program_t *node = dest;
1204         while(node->next != NULL)
1205             node = node->next;
1206         node->next = src;
1207         return dest;
1208     }
1209     else
1210         return src;
1211 }
1212 
1213 
1214 /* ---------------------------------------------
1215  * utilities
1216  * ---------------------------------------------- */
1217 
dirpath(const char * filepath)1218 char* dirpath(const char *filepath)
1219 {
1220     char *p, *str = str_dup(filepath);
1221 
1222     if(NULL == (p=strrchr(str, '/'))) {
1223         if(NULL == (p=strrchr(str, '\\'))) {
1224             *str = 0;
1225             return str;
1226         }
1227     }
1228 
1229     *(++p) = 0;
1230     return str;
1231 }
1232 
error(const char * fmt,...)1233 void error(const char *fmt, ...)
1234 {
1235     char buf[1024] = "nanoparser error! ";
1236     int len = strlen(buf);
1237     va_list args;
1238 
1239     va_start(args, fmt);
1240     vsprintf(buf+len, fmt, args);
1241     va_end(args);
1242 
1243     if(error_fun)
1244         error_fun(buf);
1245     else
1246         fprintf(stderr, "%s\n", buf);
1247 
1248     exit(1);
1249 }
1250 
warning(const char * fmt,...)1251 void warning(const char *fmt, ...)
1252 {
1253     char buf[1024] = "nanoparser warning! ";
1254     int len = strlen(buf);
1255     va_list args;
1256 
1257     va_start(args, fmt);
1258     vsprintf(buf+len, fmt, args);
1259     va_end(args);
1260 
1261     if(warning_fun)
1262         warning_fun(buf);
1263     else
1264         fprintf(stderr, "%s\n", buf);
1265 }
1266 
str_dup(const char * s)1267 char* str_dup(const char *s)
1268 {
1269     char *p = malloc_x( (1 + strlen(s)) * sizeof(char) );
1270     return strcpy(p, s);
1271 }
1272 
r_trim(char * s)1273 char* r_trim(char *s)
1274 {
1275     char *p;
1276 
1277     if(NULL != (p=strrchr(s, ' ')))
1278         *p = 0;
1279 
1280     if(NULL != (p=strrchr(s, '\t')))
1281         *p = 0;
1282 
1283     return p;
1284 }
1285 
malloc_x(size_t bytes)1286 void* malloc_x(size_t bytes)
1287 {
1288     void *m = malloc(bytes);
1289 
1290     if(m == NULL) {
1291         fprintf(stderr, __FILE__ ": Out of memory");
1292         error(__FILE__ ": Out of memory");
1293         exit(1);
1294     }
1295 
1296     return m;
1297 }
1298 
realloc_x(void * ptr,size_t bytes)1299 void* realloc_x(void *ptr, size_t bytes)
1300 {
1301     void *m = realloc(ptr, bytes);
1302 
1303     if(m == NULL) {
1304         fprintf(stderr, __FILE__ ": Out of memory (realloc_x)");
1305         error(__FILE__ ": Out of memory (realloc_x)");
1306         exit(1);
1307     }
1308 
1309     return m;
1310 }
1311 
1312 #ifdef __cplusplus
1313 }
1314 #endif
1315 
1316