3 * Asterisk -- An open source telephony toolkit.
5 * Copyright (C) 2006, Digium, Inc.
7 * Steve Murphy <murf@parsetree.com>
9 * See http://www.asterisk.org for more information about
10 * the Asterisk project. Please do not directly contact
11 * any of the maintainers of this project for assistance;
12 * the project provides a web site, mailing lists and IRC
13 * channels for your use.
15 * This program is free software, distributed under the terms of
16 * the GNU General Public License Version 2. See the LICENSE file
17 * at the top of the source tree.
21 * \brief Bison Grammar description of AEL2.
27 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
33 #include "asterisk/logger.h"
34 #include "asterisk/lock.h"
35 #include "asterisk/hashtab.h"
36 #include "asterisk/ael_structs.h"
37 #include "asterisk/utils.h"
39 extern struct ast_flags ast_compat;
41 pval * linku1(pval *head, pval *tail);
42 static void set_dads(pval *dad, pval *child_list);
43 void reset_parencount(yyscan_t yyscanner);
44 void reset_semicount(yyscan_t yyscanner);
45 void reset_argcount(yyscan_t yyscanner );
47 #define YYLEX_PARAM ((struct parse_io *)parseio)->scanner
48 #define YYERROR_VERBOSE 1
52 int ael_is_funcname(char *name);
54 static char *ael_token_subst(const char *mess);
60 int intval; /* integer value, typically flags */
61 char *str; /* strings */
62 struct pval *pval; /* full objects */
66 /* declaring these AFTER the union makes things a lot simpler! */
67 void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s);
68 int ael_yylex (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , void * yyscanner);
70 /* create a new object with start-end marker */
71 pval *npval(pvaltype type, int first_line, int last_line,
72 int first_column, int last_column);
74 /* create a new object with start-end marker, simplified interface.
75 * Must be declared here because YYLTYPE is not known before
77 static pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last);
79 /* another frontend for npval, this time for a string */
80 static pval *nword(char *string, YYLTYPE *pos);
82 /* update end position of an object, return the object */
83 static pval *update_last(pval *, YYLTYPE *);
87 %token KW_CONTEXT LC RC LP RP SEMI EQ COMMA COLON AMPER BAR AT
88 %token KW_MACRO KW_GLOBALS KW_IGNOREPAT KW_SWITCH KW_IF KW_IFTIME KW_ELSE KW_RANDOM KW_ABSTRACT KW_EXTEND
89 %token EXTENMARK KW_GOTO KW_JUMP KW_RETURN KW_BREAK KW_CONTINUE KW_REGEXTEN KW_HINT
90 %token KW_FOR KW_WHILE KW_CASE KW_PATTERN KW_DEFAULT KW_CATCH KW_SWITCHES KW_ESWITCHES
91 %token KW_INCLUDES KW_LOCAL
98 %type <pval>includeslist
99 %type <pval>switchlist
100 %type <pval>eswitches
102 %type <pval>macro_statement
103 %type <pval>macro_statements
104 %type <pval>case_statement
105 %type <pval>case_statements
106 %type <pval>eval_arglist
107 %type <pval>application_call
108 %type <pval>application_call_head
109 %type <pval>macro_call
110 %type <pval>target jumptarget
111 %type <pval>statement
112 %type <pval>switch_statement
114 %type <pval>if_like_head
115 %type <pval>statements
116 %type <pval>extension
117 %type <pval>ignorepat
121 %type <pval>assignment
122 %type <pval>local_assignment
123 %type <pval>global_statements
133 %type <pval>included_entry
136 %type <str>context_name
141 %type <str>word3_list hint_word
145 %type <intval>opt_abstract
151 %locations /* track source location using @n variables (yylloc in flex) */
152 %pure-parser /* pass yylval and yylloc as arguments to yylex(). */
153 %name-prefix="ael_yy"
155 * add an additional argument, parseio, to yyparse(),
156 * which is then accessible in the grammar actions
158 %parse-param {struct parse_io *parseio}
160 /* there will be two shift/reduce conflicts, they involve the if statement, where a single statement occurs not wrapped in curlies in the "true" section
161 the default action to shift will attach the else to the preceeding if. */
166 * declare destructors for objects.
167 * The former is for pval, the latter for strings.
168 * NOTE: we must not have a destructor for a 'file' object.
173 } includes includeslist switchlist eswitches switches
174 macro_statement macro_statements case_statement case_statements
175 eval_arglist application_call application_call_head
176 macro_call target jumptarget statement switch_statement
177 if_like_head statements extension
178 ignorepat element elements arglist assignment local_assignment
179 global_statements globals macro context object objects
181 timespec included_entry
183 %destructor { free($$);} word word_list goto_word word3_list opt_word context_name
191 file : objects { $$ = parseio->pval = $1; }
194 objects : object {$$=$1;}
195 | objects object { $$ = linku1($1, $2); }
196 | objects error {$$=$1;}
199 object : context {$$=$1;}
202 | SEMI {$$=0;/* allow older docs to be read */}
205 context_name : word { $$ = $1; }
206 | KW_DEFAULT { $$ = strdup("default"); }
209 context : opt_abstract KW_CONTEXT context_name LC elements RC {
210 $$ = npval2(PV_CONTEXT, &@1, &@6);
212 $$->u2.statements = $5;
214 $$->u3.abstract = $1;}
217 /* optional "abstract" keyword XXX there is no regression test for this */
218 opt_abstract: KW_ABSTRACT { $$ = 1; }
219 | /* nothing */ { $$ = 0; }
220 | KW_EXTEND { $$ = 2; }
221 | KW_EXTEND KW_ABSTRACT { $$=3; }
222 | KW_ABSTRACT KW_EXTEND { $$=3; }
225 macro : KW_MACRO word LP arglist RP LC macro_statements RC {
226 $$ = npval2(PV_MACRO, &@1, &@8);
227 $$->u1.str = $2; $$->u2.arglist = $4; $$->u3.macro_statements = $7;
231 globals : KW_GLOBALS LC global_statements RC {
232 $$ = npval2(PV_GLOBALS, &@1, &@4);
233 $$->u1.statements = $3;
237 global_statements : { $$ = NULL; }
238 | global_statements assignment {$$ = linku1($1, $2); }
239 | error global_statements {$$=$2;}
242 assignment : word EQ { reset_semicount(parseio->scanner); } word SEMI {
243 $$ = npval2(PV_VARDEC, &@1, &@5);
248 local_assignment : KW_LOCAL word EQ { reset_semicount(parseio->scanner); } word SEMI {
249 $$ = npval2(PV_LOCALVARDEC, &@1, &@6);
254 /* XXX this matches missing arguments, is this desired ? */
255 arglist : /* empty */ { $$ = NULL; }
256 | word { $$ = nword($1, &@1); }
257 | arglist COMMA word { $$ = linku1($1, nword($3, &@3)); }
258 | arglist error {$$=$1;}
262 | elements element { $$ = linku1($1, $2); }
263 | error elements { $$=$2;}
266 element : extension {$$=$1;}
271 | assignment {$$=$1;}
272 | local_assignment {$$=$1;}
273 | word error {free($1); $$=0;}
274 | SEMI {$$=0;/* allow older docs to be read */}
277 ignorepat : KW_IGNOREPAT EXTENMARK word SEMI {
278 $$ = npval2(PV_IGNOREPAT, &@1, &@4);
282 extension : word EXTENMARK statement {
283 $$ = npval2(PV_EXTENSION, &@1, &@3);
285 $$->u2.statements = $3; set_dads($$,$3);}
286 | word AT word EXTENMARK statement {
287 $$ = npval2(PV_EXTENSION, &@1, &@3);
288 $$->u1.str = malloc(strlen($1)+strlen($3)+2);
289 strcpy($$->u1.str,$1);
290 strcat($$->u1.str,"@");
291 strcat($$->u1.str,$3);
293 $$->u2.statements = $5; set_dads($$,$5);}
294 | KW_REGEXTEN word EXTENMARK statement {
295 $$ = npval2(PV_EXTENSION, &@1, &@4);
297 $$->u2.statements = $4; set_dads($$,$4);
299 | KW_HINT LP hint_word RP word EXTENMARK statement {
300 $$ = npval2(PV_EXTENSION, &@1, &@7);
302 $$->u2.statements = $7; set_dads($$,$7);
304 | KW_REGEXTEN KW_HINT LP hint_word RP word EXTENMARK statement {
305 $$ = npval2(PV_EXTENSION, &@1, &@8);
307 $$->u2.statements = $8; set_dads($$,$8);
312 /* list of statements in a block or after a case label - can be empty */
313 statements : /* empty */ { $$ = NULL; }
314 | statements statement { $$ = linku1($1, $2); }
315 | error statements {$$=$2;}
318 /* hh:mm-hh:mm, due to the way the parser works we do not
319 * detect the '-' but only the ':' as separator
321 timerange: word3_list COLON word3_list COLON word3_list {
322 if (asprintf(&$$, "%s:%s:%s", $1, $3, $5) < 0) {
323 ast_log(LOG_WARNING, "asprintf() failed\n");
334 /* full time specification range|dow|*|* */
335 timespec : timerange BAR word3_list BAR word3_list BAR word3_list {
337 $$->next = nword($3, &@3);
338 $$->next->next = nword($5, &@5);
339 $$->next->next->next = nword($7, &@7); }
342 /* expression used in if, random, while, switch */
343 test_expr : LP { reset_parencount(parseio->scanner); } word_list RP { $$ = $3; }
346 /* 'if' like statements: if, iftime, random */
347 if_like_head : KW_IF test_expr {
348 $$= npval2(PV_IF, &@1, &@2);
350 | KW_RANDOM test_expr {
351 $$ = npval2(PV_RANDOM, &@1, &@2);
353 | KW_IFTIME LP timespec RP {
354 $$ = npval2(PV_IFTIME, &@1, &@4);
359 /* word_list is a hack to fix a problem with context switching between bison and flex;
360 by the time you register a new context with flex, you've already got a look-ahead token
361 from the old context, with no way to put it back and start afresh. So, we kludge this
362 and merge the words back together. */
364 word_list : word { $$ = $1;}
366 if (asprintf(&($$), "%s%s", $1, $2) < 0) {
367 ast_log(LOG_WARNING, "asprintf() failed\n");
377 hint_word : word { $$ = $1; }
379 if (asprintf(&($$), "%s %s", $1, $2) < 0) {
380 ast_log(LOG_WARNING, "asprintf() failed\n");
387 | hint_word COLON word {
388 if (asprintf(&($$), "%s:%s", $1, $3) < 0) {
389 ast_log(LOG_WARNING, "asprintf() failed\n");
396 | hint_word AMPER word { /* there are often '&' in hints */
397 if (asprintf(&($$), "%s&%s", $1, $3) < 0) {
398 ast_log(LOG_WARNING, "asprintf() failed\n");
405 | hint_word AT word {
406 if (asprintf(&($$), "%s@%s", $1, $3) < 0) {
407 ast_log(LOG_WARNING, "asprintf() failed\n");
416 word3_list : word { $$ = $1;}
418 if (asprintf(&($$), "%s%s", $1, $2) < 0) {
419 ast_log(LOG_WARNING, "asprintf() failed\n");
428 if (asprintf(&($$), "%s%s%s", $1, $2, $3) < 0) {
429 ast_log(LOG_WARNING, "asprintf() failed\n");
440 goto_word : word { $$ = $1;}
442 if (asprintf(&($$), "%s%s", $1, $2) < 0) {
443 ast_log(LOG_WARNING, "asprintf() failed\n");
450 | goto_word COLON word {
451 if (asprintf(&($$), "%s:%s", $1, $3) < 0) {
452 ast_log(LOG_WARNING, "asprintf() failed\n");
461 switch_statement : KW_SWITCH test_expr LC case_statements RC {
462 $$ = npval2(PV_SWITCH, &@1, &@5);
464 $$->u2.statements = $4; set_dads($$,$4);}
468 * Definition of a statememt in our language
470 statement : LC statements RC {
471 $$ = npval2(PV_STATEMENTBLOCK, &@1, &@3);
472 $$->u1.list = $2; set_dads($$,$2);}
473 | assignment { $$ = $1; }
474 | local_assignment { $$ = $1; }
475 | KW_GOTO target SEMI {
476 $$ = npval2(PV_GOTO, &@1, &@3);
478 | KW_JUMP jumptarget SEMI {
479 $$ = npval2(PV_GOTO, &@1, &@3);
482 $$ = npval2(PV_LABEL, &@1, &@2);
484 | KW_FOR LP {reset_semicount(parseio->scanner);} word SEMI
485 {reset_semicount(parseio->scanner);} word SEMI
486 {reset_parencount(parseio->scanner);} word RP statement { /* XXX word_list maybe ? */
487 $$ = npval2(PV_FOR, &@1, &@12);
488 $$->u1.for_init = $4;
490 $$->u3.for_inc = $10;
491 $$->u4.for_statements = $12; set_dads($$,$12);}
492 | KW_WHILE test_expr statement {
493 $$ = npval2(PV_WHILE, &@1, &@3);
495 $$->u2.statements = $3; set_dads($$,$3);}
496 | switch_statement { $$ = $1; }
497 | AMPER macro_call SEMI { $$ = update_last($2, &@2); }
498 | application_call SEMI { $$ = update_last($1, &@2); }
500 $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
502 | application_call EQ {reset_semicount(parseio->scanner);} word SEMI {
506 $$ = npval2(PV_VARDEC, &@1, &@5);
508 /* rebuild the original string-- this is not an app call, it's an unwrapped vardec, with a func call on the LHS */
509 /* string to big to fit in the buffer? */
510 tot+=strlen($1->u1.str);
511 for(pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
512 tot+=strlen(pptr->u1.str);
513 tot++; /* for a sep like a comma */
515 tot+=4; /* for safety */
516 bufx = calloc(1, tot);
517 strcpy(bufx,$1->u1.str);
519 /* XXX need to advance the pointer or the loop is very inefficient */
520 for (pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
521 if ( pptr != $1->u2.arglist )
523 strcat(bufx,pptr->u1.str);
527 if ( !ael_is_funcname($1->u1.str) )
528 ast_log(LOG_WARNING, "==== File: %s, Line %d, Cols: %d-%d: Function call? The name %s is not in my internal list of function names\n",
529 my_file, @1.first_line, @1.first_column, @1.last_column, $1->u1.str);
532 destroy_pval($1); /* the app call it is not, get rid of that chain */
535 | KW_BREAK SEMI { $$ = npval2(PV_BREAK, &@1, &@2); }
536 | KW_RETURN SEMI { $$ = npval2(PV_RETURN, &@1, &@2); }
537 | KW_CONTINUE SEMI { $$ = npval2(PV_CONTINUE, &@1, &@2); }
538 | if_like_head statement opt_else {
539 $$ = update_last($1, &@2);
540 $$->u2.statements = $2; set_dads($$,$2);
541 $$->u3.else_statements = $3;set_dads($$,$3);}
545 opt_else : KW_ELSE statement { $$ = $2; }
549 target : goto_word { $$ = nword($1, &@1); }
550 | goto_word BAR goto_word {
552 $$->next = nword($3, &@3); }
553 | goto_word COMMA goto_word {
555 $$->next = nword($3, &@3); }
556 | goto_word BAR goto_word BAR goto_word {
558 $$->next = nword($3, &@3);
559 $$->next->next = nword($5, &@5); }
560 | goto_word COMMA goto_word COMMA goto_word {
562 $$->next = nword($3, &@3);
563 $$->next->next = nword($5, &@5); }
564 | KW_DEFAULT BAR goto_word BAR goto_word {
565 $$ = nword(strdup("default"), &@1);
566 $$->next = nword($3, &@3);
567 $$->next->next = nword($5, &@5); }
568 | KW_DEFAULT COMMA goto_word COMMA goto_word {
569 $$ = nword(strdup("default"), &@1);
570 $$->next = nword($3, &@3);
571 $$->next->next = nword($5, &@5); }
574 opt_pri : /* empty */ { $$ = strdup("1"); }
575 | COMMA word { $$ = $2; }
578 /* XXX please document the form of jumptarget */
579 jumptarget : goto_word opt_pri { /* ext[, pri] default 1 */
581 $$->next = nword($2, &@2); } /* jump extension[,priority][@context] */
582 | goto_word opt_pri AT context_name { /* context, ext, pri */
584 $$->next = nword($1, &@1);
585 $$->next->next = nword($2, &@2); }
588 macro_call : word LP {reset_argcount(parseio->scanner);} eval_arglist RP {
589 /* XXX original code had @2 but i think we need @5 */
590 $$ = npval2(PV_MACRO_CALL, &@1, &@5);
592 $$->u2.arglist = $4;}
594 $$= npval2(PV_MACRO_CALL, &@1, &@3);
598 /* XXX application_call_head must be revised. Having 'word LP { ...'
599 * just as above should work fine, however it gives a different result.
601 application_call_head: word LP {reset_argcount(parseio->scanner);} {
602 if (strcasecmp($1,"goto") == 0) {
603 $$ = npval2(PV_GOTO, &@1, &@2);
604 free($1); /* won't be using this */
605 ast_log(LOG_WARNING, "==== File: %s, Line %d, Cols: %d-%d: Suggestion: Use the goto statement instead of the Goto() application call in AEL.\n", my_file, @1.first_line, @1.first_column, @1.last_column );
607 $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
612 application_call : application_call_head eval_arglist RP {
613 $$ = update_last($1, &@3);
614 if( $$->type == PV_GOTO )
619 | application_call_head RP { $$ = update_last($1, &@2); }
622 opt_word : word { $$ = $1 }
623 | { $$ = strdup(""); }
626 eval_arglist : word_list { $$ = nword($1, &@1); }
628 $$= npval(PV_WORD,0/*@1.first_line*/,0/*@1.last_line*/,0/* @1.first_column*/, 0/*@1.last_column*/);
629 $$->u1.str = strdup(""); }
630 | eval_arglist COMMA opt_word { $$ = linku1($1, nword($3, &@3)); }
633 case_statements: /* empty */ { $$ = NULL; }
634 | case_statements case_statement { $$ = linku1($1, $2); }
637 case_statement: KW_CASE word COLON statements {
638 $$ = npval2(PV_CASE, &@1, &@3); /* XXX 3 or 4 ? */
640 $$->u2.statements = $4; set_dads($$,$4);}
641 | KW_DEFAULT COLON statements {
642 $$ = npval2(PV_DEFAULT, &@1, &@3);
644 $$->u2.statements = $3;set_dads($$,$3);}
645 | KW_PATTERN word COLON statements {
646 $$ = npval2(PV_PATTERN, &@1, &@4); /* XXX@3 or @4 ? */
648 $$->u2.statements = $4;set_dads($$,$4);}
651 macro_statements: /* empty */ { $$ = NULL; }
652 | macro_statements macro_statement { $$ = linku1($1, $2); }
655 macro_statement : statement {$$=$1;}
657 | KW_CATCH word LC statements RC {
658 $$ = npval2(PV_CATCH, &@1, &@5);
660 $$->u2.statements = $4; set_dads($$,$4);}
663 switches : KW_SWITCHES LC switchlist RC {
664 $$ = npval2(PV_SWITCHES, &@1, &@2);
665 $$->u1.list = $3; set_dads($$,$3);}
668 eswitches : KW_ESWITCHES LC switchlist RC {
669 $$ = npval2(PV_ESWITCHES, &@1, &@2);
670 $$->u1.list = $3; set_dads($$,$3);}
673 switchlist : /* empty */ { $$ = NULL; }
674 | switchlist word SEMI { $$ = linku1($1,nword($2, &@2)); }
675 | switchlist word AT word SEMI {
677 if (asprintf(&x,"%s@%s", $2, $4) < 0) {
678 ast_log(LOG_WARNING, "asprintf() failed\n");
683 $$ = linku1($1,nword(x, &@2));
686 | error switchlist {$$=$2;}
689 included_entry : context_name { $$ = nword($1, &@1); }
690 | context_name BAR timespec {
693 prev_word=0; /* XXX sure ? */ }
696 /* list of ';' separated context names followed by optional timespec */
697 includeslist : included_entry SEMI { $$ = $1; }
698 | includeslist included_entry SEMI { $$ = linku1($1, $2); }
699 | includeslist error {$$=$1;}
702 includes : KW_INCLUDES LC includeslist RC {
703 $$ = npval2(PV_INCLUDES, &@1, &@4);
704 $$->u1.list = $3;set_dads($$,$3);}
705 | KW_INCLUDES LC RC {
706 $$ = npval2(PV_INCLUDES, &@1, &@3);}
712 static char *token_equivs1[] =
752 static char *token_equivs2[] =
793 static char *ael_token_subst(const char *mess)
795 /* calc a length, malloc, fill, and return; yyerror had better free it! */
799 int token_equivs_entries = sizeof(token_equivs1)/sizeof(char*);
801 for (p=mess; *p; p++) {
802 for (i=0; i<token_equivs_entries; i++) {
803 if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 )
805 len+=strlen(token_equivs2[i])+2;
806 p += strlen(token_equivs1[i])-1;
812 res = calloc(1, len+1);
817 for (i=0; i<token_equivs_entries; i++) {
818 if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) {
820 for (t=token_equivs2[i]; *t;) {
824 p += strlen(token_equivs1[i]);
836 void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s)
838 char *s2 = ael_token_subst((char *)s);
839 if (locp->first_line == locp->last_line) {
840 ast_log(LOG_ERROR, "==== File: %s, Line %d, Cols: %d-%d: Error: %s\n", my_file, locp->first_line, locp->first_column, locp->last_column, s2);
842 ast_log(LOG_ERROR, "==== File: %s, Line %d Col %d to Line %d Col %d: Error: %s\n", my_file, locp->first_line, locp->first_column, locp->last_line, locp->last_column, s2);
845 parseio->syntax_error_count++;
848 struct pval *npval(pvaltype type, int first_line, int last_line,
849 int first_column, int last_column)
851 pval *z = calloc(1, sizeof(struct pval));
853 z->startline = first_line;
854 z->endline = last_line;
855 z->startcol = first_column;
856 z->endcol = last_column;
857 z->filename = strdup(my_file);
861 static struct pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last)
863 return npval(type, first->first_line, last->last_line,
864 first->first_column, last->last_column);
867 static struct pval *update_last(pval *obj, YYLTYPE *last)
869 obj->endline = last->last_line;
870 obj->endcol = last->last_column;
874 /* frontend for npval to create a PV_WORD string from the given token */
875 static pval *nword(char *string, YYLTYPE *pos)
877 pval *p = npval2(PV_WORD, pos, pos);
883 /* this routine adds a dad ptr to each element in the list */
884 static void set_dads(struct pval *dad, struct pval *child_list)
888 for(t=child_list;t;t=t->next) /* simple stuff */