This commit is a basic AEL enhancement: c-style comments
[asterisk/asterisk.git] / pbx / ael / ael.flex
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2006, Digium, Inc.
5  *
6  * Steve Murphy <murf@parsetree.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18 /*! \file
19  *
20  * \brief Flex scanner description of tokens used in AEL2 .
21  *
22  */
23
24 /*
25  * Start with flex options:
26  *
27  * %x describes the contexts we have: paren, semic and argg, plus INITIAL
28  */
29 %x paren semic argg  comment
30
31 /* prefix used for various globally-visible functions and variables.
32  * This renames also yywrap, but since we do not use it, we just
33  * add option noyywrap to remove it.
34  */
35 %option prefix="ael_yy"
36 %option noyywrap
37
38 /* batch gives a bit more performance if we are using it in
39  * a non-interactive mode. We probably don't care much.
40  */
41 %option batch
42
43 /* outfile is the filename to be used instead of lex.yy.c */
44 %option outfile="ael_lex.c"
45
46 /*
47  * These are not supported in flex 2.5.4, but we need them
48  * at the moment:
49  * reentrant produces a thread-safe parser. Not 100% sure that
50  * we require it, though.
51  * bison-bridge passes an additional yylval argument to yylex().
52  * bison-locations is probably not needed.
53  */
54 %option reentrant
55 %option bison-bridge
56 %option bison-locations
57
58 %{
59 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
60
61 #include <sys/types.h>
62 #include <sys/stat.h>
63 #include <unistd.h>
64
65 #include "asterisk/logger.h"
66 #include "asterisk/utils.h"
67 #include "ael/ael.tab.h"
68 #include "asterisk/ael_structs.h"
69
70 /*
71  * A stack to keep track of matching brackets ( [ { } ] )
72  */
73 static char pbcstack[400];      /* XXX missing size checks */
74 static int pbcpos = 0;
75 static void pbcpush(char x);
76 static int pbcpop(char x);
77
78 static int parencount = 0;
79
80 /*
81  * current line, column and filename, updated as we read the input.
82  */
83 static int my_lineno = 1;       /* current line in the source */
84 static int my_col = 1;          /* current column in the source */
85 char *my_file = 0;              /* used also in the bison code */
86 char *prev_word;                /* XXX document it */
87
88 #define MAX_INCLUDE_DEPTH 50
89
90 /*
91  * flex is not too smart, and generates global functions
92  * without prototypes so the compiler may complain.
93  * To avoid that, we declare the prototypes here,
94  * even though these functions are not used.
95  */
96 int ael_yyget_column  (yyscan_t yyscanner);
97 void ael_yyset_column (int  column_no , yyscan_t yyscanner);
98
99 int ael_yyparse (struct parse_io *);
100
101 /*
102  * A stack to process include files.
103  * As we switch into the new file we need to store the previous
104  * state to restore it later.
105  */
106 struct stackelement {
107         char *fname;
108         int lineno;
109         int colno;
110         YY_BUFFER_STATE bufstate;
111 };
112
113 static struct stackelement  include_stack[MAX_INCLUDE_DEPTH];
114 static int include_stack_index = 0;
115
116 /*
117  * if we use the @n feature of bison, we must supply the start/end
118  * location of tokens in the structure pointed by yylloc.
119  * Simple tokens are just assumed to be on the same line, so
120  * the line number is constant, and the column is incremented
121  * by the length of the token.
122  */
123 #ifdef FLEX_BETA        /* set for 2.5.33 */
124
125 /* compute the total number of lines and columns in the text
126  * passed as argument.
127  */
128 static void pbcwhere(const char *text, int *line, int *col )
129 {
130         int loc_line = *line;
131         int loc_col = *col;
132         char c;
133         while ( (c = *text++) ) {
134                 if ( c == '\t' ) {
135                         loc_col += 8 - (loc_col % 8);
136                 } else if ( c == '\n' ) {
137                         loc_line++;
138                         loc_col = 1;
139                 } else
140                         loc_col++;
141         }
142         *line = loc_line;
143         *col = loc_col;
144 }
145
146 #define STORE_POS do {                                                  \
147                 yylloc->first_line = yylloc->last_line = my_lineno;     \
148                 yylloc->first_column=my_col;                            \
149                 yylloc->last_column=my_col+yyleng-1;                    \
150                 my_col+=yyleng;                                         \
151         } while (0)
152
153 #define STORE_LOC do {                                  \
154                 yylloc->first_line = my_lineno;         \
155                 yylloc->first_column=my_col;            \
156                 pbcwhere(yytext, &my_lineno, &my_col);  \
157                 yylloc->last_line = my_lineno;          \
158                 yylloc->last_column = my_col - 1;       \
159         } while (0)
160 #else
161 #define STORE_POS
162 #define STORE_LOC
163 #endif
164 %}
165
166
167 NOPARENS        ([^()\[\]\{\}]|\\[()\[\]\{\}])*
168
169 NOARGG          ([^(),\{\}\[\]]|\\[,()\[\]\{\}])*
170
171 NOSEMIC         ([^;()\{\}\[\]]|\\[;()\[\]\{\}])*
172
173 %%
174
175 \{              { STORE_POS; return LC;}
176 \}              { STORE_POS; return RC;}
177 \(              { STORE_POS; return LP;}
178 \)              { STORE_POS; return RP;}
179 \;              { STORE_POS; return SEMI;}
180 \=              { STORE_POS; return EQ;}
181 \,              { STORE_POS; return COMMA;}
182 \:              { STORE_POS; return COLON;}
183 \&              { STORE_POS; return AMPER;}
184 \|              { STORE_POS; return BAR;}
185 \=\>            { STORE_POS; return EXTENMARK;}
186 \@              { STORE_POS; return AT;}
187 \/\/[^\n]*      {/*comment*/}
188 context         { STORE_POS; return KW_CONTEXT;}
189 abstract        { STORE_POS; return KW_ABSTRACT;}
190 macro           { STORE_POS; return KW_MACRO;};
191 globals         { STORE_POS; return KW_GLOBALS;}
192 ignorepat       { STORE_POS; return KW_IGNOREPAT;}
193 switch          { STORE_POS; return KW_SWITCH;}
194 if              { STORE_POS; return KW_IF;}
195 ifTime          { STORE_POS; return KW_IFTIME;}
196 random          { STORE_POS; return KW_RANDOM;}
197 regexten        { STORE_POS; return KW_REGEXTEN;}
198 hint            { STORE_POS; return KW_HINT;}
199 else            { STORE_POS; return KW_ELSE;}
200 goto            { STORE_POS; return KW_GOTO;}
201 jump            { STORE_POS; return KW_JUMP;}
202 return          { STORE_POS; return KW_RETURN;}
203 break           { STORE_POS; return KW_BREAK;}
204 continue        { STORE_POS; return KW_CONTINUE;}
205 for             { STORE_POS; return KW_FOR;}
206 while           { STORE_POS; return KW_WHILE;}
207 case            { STORE_POS; return KW_CASE;}
208 default         { STORE_POS; return KW_DEFAULT;}
209 pattern         { STORE_POS; return KW_PATTERN;}
210 catch           { STORE_POS; return KW_CATCH;}
211 switches        { STORE_POS; return KW_SWITCHES;}
212 eswitches       { STORE_POS; return KW_ESWITCHES;}
213 includes        { STORE_POS; return KW_INCLUDES;}
214 "/*"            { BEGIN(comment); my_col += 2; }
215
216 <comment>[^*\n]*        { my_col += yyleng; }
217 <comment>[^*\n]*\n      { ++my_lineno; my_col=1;}
218 <comment>"*"+[^*/\n]*   { my_col += yyleng; }
219 <comment>"*"+[^*/\n]*\n         { ++my_lineno; my_col=1;}
220 <comment>"*/"           { my_col += 2; BEGIN(INITIAL); }
221
222 \n              { my_lineno++; my_col = 1; }
223 [ ]+            { my_col += yyleng; }
224 [\t]+           { my_col += (yyleng*8)-(my_col%8); }
225
226 [-a-zA-Z0-9'"_/.\<\>\*\+!$#\[\]][-a-zA-Z0-9'"_/.!\*\+\<\>\{\}$#\[\]]*   {
227                 STORE_POS;
228                 yylval->str = strdup(yytext);
229                 prev_word = yylval->str;
230                 return word;
231         }
232
233
234
235         /*
236          * context used for arguments of if_head, random_head, switch_head,
237          * for (last statement), while (XXX why not iftime_head ?).
238          * End with the matching parentheses.
239          * A comma at the top level is valid here, unlike in argg where it
240          * is an argument separator so it must be returned as a token.
241          */
242 <paren>{NOPARENS}\)     {
243                 if ( pbcpop(')') ) {    /* error */
244                         STORE_LOC;
245                         ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched ')' in expression: %s !\n", my_file, my_lineno, my_col, yytext);
246                         BEGIN(0);
247                         yylval->str = strdup(yytext);
248                         prev_word = 0;
249                         return word;
250                 }
251                 parencount--;
252                 if ( parencount >= 0) {
253                         yymore();
254                 } else {
255                         STORE_LOC;
256                         yylval->str = strdup(yytext);
257                         yylval->str[yyleng-1] = '\0'; /* trim trailing ')' */
258                         unput(')');
259                         BEGIN(0);
260                         return word;
261                 }
262         }
263
264 <paren>{NOPARENS}[\(\[\{]       {
265                 char c = yytext[yyleng-1];
266                 if (c == '(')
267                         parencount++;
268                 pbcpush(c);
269                 yymore();
270         }
271
272 <paren>{NOPARENS}[\]\}] {
273                 char c = yytext[yyleng-1];
274                 if ( pbcpop(c))  { /* error */
275                         STORE_LOC;
276                         ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n",
277                                 my_file, my_lineno, my_col, c);
278                         BEGIN(0);
279                         yylval->str = strdup(yytext);
280                         return word;
281                 }
282                 yymore();
283         }
284
285
286         /*
287          * handlers for arguments to a macro or application calls.
288          * We enter this context when we find the initial '(' and
289          * stay here until we close all matching parentheses,
290          * and find the comma (argument separator) or the closing ')'
291          * of the (external) call, which happens when parencount == 0
292          * before the decrement.
293          */
294 <argg>{NOARGG}[\(\[\{]    {
295                 char c = yytext[yyleng-1];
296                 if (c == '(')
297                         parencount++;
298                 pbcpush(c);
299                 yymore();
300         }
301
302 <argg>{NOARGG}\)        {
303                 if ( pbcpop(')') ) { /* error */
304                         STORE_LOC;
305                         ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched ')' in expression!\n", my_file, my_lineno, my_col);
306                         BEGIN(0);
307                         yylval->str = strdup(yytext);
308                         return word;
309                 }
310
311                 parencount--;
312                 if( parencount >= 0){
313                         yymore();
314                 } else {
315                         STORE_LOC;
316                         BEGIN(0);
317                         if ( !strcmp(yytext, ")") )
318                                 return RP;
319                         yylval->str = strdup(yytext);
320                         yylval->str[yyleng-1] = '\0'; /* trim trailing ')' */
321                         unput(')');
322                         return word;
323                 }
324         }
325
326 <argg>{NOARGG}\,        {
327                 if( parencount != 0) { /* printf("Folding in a comma!\n"); */
328                         yymore();
329                 } else  {
330                         STORE_LOC;
331                         if( !strcmp(yytext,"," ) )
332                                 return COMMA;
333                         yylval->str = strdup(yytext);
334                         yylval->str[yyleng-1] = '\0';
335                         unput(',');
336                         return word;
337                 }
338         }
339
340 <argg>{NOARGG}[\]\}]    {
341                 char c = yytext[yyleng-1];
342                 if ( pbcpop(c) ) { /* error */
343                         STORE_LOC;
344                         ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n", my_file, my_lineno, my_col, c);
345                         BEGIN(0);
346                         yylval->str = strdup(yytext);
347                         return word;
348                 }
349                 yymore();
350         }
351
352         /*
353          * context used to find tokens in the right hand side of assignments,
354          * or in the first and second operand of a 'for'. As above, match
355          * commas and use ';' as a separator (hence return it as a separate token).
356          */
357 <semic>{NOSEMIC}[\(\[\{]        {
358                 char c = yytext[yyleng-1];
359                 yymore();
360                 pbcpush(c);
361         }
362
363 <semic>{NOSEMIC}[\)\]\}]        {
364                 char c = yytext[yyleng-1];
365                 if ( pbcpop(c) ) { /* error */
366                         STORE_LOC;
367                         ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Mismatched '%c' in expression!\n", my_file, my_lineno, my_col, c);
368                         BEGIN(0);
369                         yylval->str = strdup(yytext);
370                         return word;
371                 }
372                 yymore();
373         }
374
375 <semic>{NOSEMIC};       {
376                 STORE_LOC;
377                 yylval->str = strdup(yytext);
378                 yylval->str[yyleng-1] = '\0';
379                 unput(';');
380                 BEGIN(0);
381                 return word;
382         }
383
384 \#include[ \t]+\"[^\"]+\" {
385                 FILE *in1;
386                 char fnamebuf[1024],*p1,*p2;
387                 int error = 1;  /* don't use the file if set */
388                 p1 = strchr(yytext,'"');
389                 p2 = strrchr(yytext,'"');
390                 if ( include_stack_index >= MAX_INCLUDE_DEPTH ) {
391                         ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Includes nested too deeply! Wow!!! How did you do that?\n", my_file, my_lineno, my_col);
392                 } else if ( (int)(p2-p1) > sizeof(fnamebuf) - 1 ) {
393                         ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Filename is incredibly way too long (%d chars!). Inclusion ignored!\n", my_file, my_lineno, my_col, yyleng - 10);
394                 } else {
395                         int i;
396                         strncpy(fnamebuf, p1, p2-p1);
397                         fnamebuf[p2-p1] = 0;
398                         for (i=0; i<include_stack_index; i++) {
399                                 if ( !strcmp(fnamebuf,include_stack[i].fname )) {
400                                         ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Nice Try!!! But %s has already been included (perhaps by another file), and would cause an infinite loop of file inclusions!!! Include directive ignored\n",
401                                                 my_file, my_lineno, my_col, fnamebuf);
402                                         break;
403                                 }
404                         }
405                         if (i == include_stack_index)
406                                 error = 0;      /* we can use this file */
407                 }
408                 if ( !error ) { /* valid file name */
409                         *p2 = 0;
410                         /* relative vs. absolute */
411                         if (*(p1+1) != '/')
412                                 snprintf(fnamebuf, sizeof(fnamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, p1 + 1);
413                         else
414 #ifdef STANDALONE
415                                 strncpy(fnamebuf, p1 + 1, sizeof(fnamebuf) - 1);
416 #else
417                                 ast_copy_string(fnamebuf, p1 + 1, sizeof(fnamebuf));
418 #endif
419                         in1 = fopen( fnamebuf, "r" );
420                         if ( ! in1 ) {
421                                 ast_log(LOG_ERROR,"File=%s, line=%d, column=%d: Couldn't find the include file: %s; ignoring the Include directive!\n", my_file, my_lineno, my_col, fnamebuf);
422                         } else {
423                                 char *buffer;
424                                 struct stat stats;
425                                 stat(fnamebuf, &stats);
426                                 buffer = (char*)malloc(stats.st_size+1);
427                                 fread(buffer, 1, stats.st_size, in1);
428                                 buffer[stats.st_size] = 0;
429                                 ast_log(LOG_NOTICE,"  --Read in included file %s, %d chars\n",fnamebuf, (int)stats.st_size);
430                                 fclose(in1);
431
432                                 include_stack[include_stack_index].fname = my_file;
433                                 my_file = strdup(fnamebuf);
434                                 include_stack[include_stack_index].lineno = my_lineno;
435                                 include_stack[include_stack_index].colno = my_col+yyleng;
436                                 include_stack[include_stack_index++].bufstate = YY_CURRENT_BUFFER;
437
438                                 yy_switch_to_buffer(ael_yy_scan_string (buffer ,yyscanner),yyscanner);
439                                 free(buffer);
440                                 my_lineno = 1;
441                                 my_col = 1;
442                                 BEGIN(INITIAL);
443                         }
444                 }
445         }
446
447 <<EOF>>         {
448                 if ( --include_stack_index < 0 ) {
449                         yyterminate();
450                 } else {
451                         free(my_file);
452                         yy_delete_buffer( YY_CURRENT_BUFFER, yyscanner );
453                         yy_switch_to_buffer(include_stack[include_stack_index].bufstate, yyscanner );
454                         my_lineno = include_stack[include_stack_index].lineno;
455                         my_col    = include_stack[include_stack_index].colno;
456                         my_file   = include_stack[include_stack_index].fname;
457                 }
458         }
459
460 %%
461
462 static void pbcpush(char x)
463 {
464         pbcstack[pbcpos++] = x;
465 }
466
467 static int pbcpop(char x)
468 {
469         if (   ( x == ')' && pbcstack[pbcpos-1] == '(' )
470                 || ( x == ']' && pbcstack[pbcpos-1] == '[' )
471                 || ( x == '}' && pbcstack[pbcpos-1] == '{' )) {
472                 pbcpos--;
473                 return 0;
474         }
475         return 1; /* error */
476 }
477
478 static int c_prevword(void)
479 {
480         char *c = prev_word;
481         if (c == NULL)
482                 return 0;
483         while ( *c ) {
484                 switch (*c) {
485                 case '{':
486                 case '[':
487                 case '(':
488                         pbcpush(*c);
489                         break;
490                 case '}':
491                 case ']':
492                 case ')':
493                         if (pbcpop(*c))
494                                 return 1;
495                         break;
496                 }
497                 c++;
498         }
499         return 0;
500 }
501
502
503 /*
504  * The following three functions, reset_*, are used in the bison
505  * code to switch context. As a consequence, we need to
506  * declare them global and add a prototype so that the
507  * compiler does not complain.
508  *
509  * NOTE: yyg is declared because it is used in the BEGIN macros,
510  * though that should be hidden as the macro changes
511  * depending on the flex options that we use - in particular,
512  * %reentrant changes the way the macro is declared;
513  * without %reentrant, BEGIN uses yystart instead of yyg
514  */
515
516 void reset_parencount(yyscan_t yyscanner );
517 void reset_parencount(yyscan_t yyscanner )
518 {
519         struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
520         parencount = 0;
521         pbcpos = 0;
522         pbcpush('(');   /* push '(' so the last pcbpop (parencount= -1) will succeed */
523         c_prevword();
524         BEGIN(paren);
525 }
526
527 void reset_semicount(yyscan_t yyscanner );
528 void reset_semicount(yyscan_t yyscanner )
529 {
530         struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
531         pbcpos = 0;
532         BEGIN(semic);
533 }
534
535 void reset_argcount(yyscan_t yyscanner );
536 void reset_argcount(yyscan_t yyscanner )
537 {
538         struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
539         parencount = 0;
540         pbcpos = 0;
541         pbcpush('(');   /* push '(' so the last pcbpop (parencount= -1) will succeed */
542         c_prevword();
543         BEGIN(argg);
544 }
545
546 /* used elsewhere, but some local vars */
547 struct pval *ael2_parse(char *filename, int *errors)
548 {
549         struct pval *pval;
550         struct parse_io *io;
551         char *buffer;
552         struct stat stats;
553         FILE *fin;
554
555         /* extern int ael_yydebug; */
556
557         io = calloc(sizeof(struct parse_io),1);
558         /* reset the global counters */
559         prev_word = 0;
560         my_lineno = 1;
561         include_stack_index=0;
562         my_col = 0;
563         /* ael_yydebug = 1; */
564         ael_yylex_init(&io->scanner);
565         fin = fopen(filename,"r");
566         if ( !fin ) {
567                 ast_log(LOG_ERROR,"File %s could not be opened\n", filename);
568                 *errors = 1;
569                 return 0;
570         }
571         my_file = strdup(filename);
572         stat(filename, &stats);
573         buffer = (char*)malloc(stats.st_size+2);
574         fread(buffer, 1, stats.st_size, fin);
575         buffer[stats.st_size]=0;
576         fclose(fin);
577
578         ael_yy_scan_string (buffer ,io->scanner);
579         ael_yyset_lineno(1 , io->scanner);
580
581         /* ael_yyset_in (fin , io->scanner);    OLD WAY */
582
583         ael_yyparse(io);
584
585
586         pval = io->pval;
587         *errors = io->syntax_error_count;
588
589         ael_yylex_destroy(io->scanner);
590         free(buffer);
591         free(io);
592
593         return pval;
594 }