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);
55 static int only_one_app_set_warning = 0;
61 int intval; /* integer value, typically flags */
62 char *str; /* strings */
63 struct pval *pval; /* full objects */
67 /* declaring these AFTER the union makes things a lot simpler! */
68 void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s);
69 int ael_yylex (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , void * yyscanner);
71 /* create a new object with start-end marker */
72 pval *npval(pvaltype type, int first_line, int last_line,
73 int first_column, int last_column);
75 /* create a new object with start-end marker, simplified interface.
76 * Must be declared here because YYLTYPE is not known before
78 static pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last);
80 /* another frontend for npval, this time for a string */
81 static pval *nword(char *string, YYLTYPE *pos);
83 /* update end position of an object, return the object */
84 static pval *update_last(pval *, YYLTYPE *);
88 %token KW_CONTEXT LC RC LP RP SEMI EQ COMMA COLON AMPER BAR AT
89 %token KW_MACRO KW_GLOBALS KW_IGNOREPAT KW_SWITCH KW_IF KW_IFTIME KW_ELSE KW_RANDOM KW_ABSTRACT KW_EXTEND
90 %token EXTENMARK KW_GOTO KW_JUMP KW_RETURN KW_BREAK KW_CONTINUE KW_REGEXTEN KW_HINT
91 %token KW_FOR KW_WHILE KW_CASE KW_PATTERN KW_DEFAULT KW_CATCH KW_SWITCHES KW_ESWITCHES
92 %token KW_INCLUDES KW_LOCAL
99 %type <pval>includeslist
100 %type <pval>switchlist
101 %type <pval>eswitches
103 %type <pval>macro_statement
104 %type <pval>macro_statements
105 %type <pval>case_statement
106 %type <pval>case_statements
107 %type <pval>eval_arglist
108 %type <pval>application_call
109 %type <pval>application_call_head
110 %type <pval>macro_call
111 %type <pval>target jumptarget
112 %type <pval>statement
113 %type <pval>switch_statement
115 %type <pval>if_like_head
116 %type <pval>statements
117 %type <pval>extension
118 %type <pval>ignorepat
122 %type <pval>assignment
123 %type <pval>local_assignment
124 %type <pval>global_statements
134 %type <pval>included_entry
137 %type <str>context_name
142 %type <str>word3_list hint_word
146 %type <intval>opt_abstract
152 %locations /* track source location using @n variables (yylloc in flex) */
153 %pure-parser /* pass yylval and yylloc as arguments to yylex(). */
154 %name-prefix="ael_yy"
156 * add an additional argument, parseio, to yyparse(),
157 * which is then accessible in the grammar actions
159 %parse-param {struct parse_io *parseio}
161 /* 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
162 the default action to shift will attach the else to the preceeding if. */
167 * declare destructors for objects.
168 * The former is for pval, the latter for strings.
169 * NOTE: we must not have a destructor for a 'file' object.
174 } includes includeslist switchlist eswitches switches
175 macro_statement macro_statements case_statement case_statements
176 eval_arglist application_call application_call_head
177 macro_call target jumptarget statement switch_statement
178 if_like_head statements extension
179 ignorepat element elements arglist assignment local_assignment
180 global_statements globals macro context object objects
182 timespec included_entry
184 %destructor { free($$);} word word_list goto_word word3_list opt_word context_name
192 file : objects { $$ = parseio->pval = $1; }
195 objects : object {$$=$1;}
196 | objects object { $$ = linku1($1, $2); }
197 | objects error {$$=$1;}
200 object : context {$$=$1;}
203 | SEMI {$$=0;/* allow older docs to be read */}
206 context_name : word { $$ = $1; }
207 | KW_DEFAULT { $$ = strdup("default"); }
210 context : opt_abstract KW_CONTEXT context_name LC elements RC {
211 $$ = npval2(PV_CONTEXT, &@1, &@6);
213 $$->u2.statements = $5;
215 $$->u3.abstract = $1;}
218 /* optional "abstract" keyword XXX there is no regression test for this */
219 opt_abstract: KW_ABSTRACT { $$ = 1; }
220 | /* nothing */ { $$ = 0; }
221 | KW_EXTEND { $$ = 2; }
222 | KW_EXTEND KW_ABSTRACT { $$=3; }
223 | KW_ABSTRACT KW_EXTEND { $$=3; }
226 macro : KW_MACRO word LP arglist RP LC macro_statements RC {
227 $$ = npval2(PV_MACRO, &@1, &@8);
228 $$->u1.str = $2; $$->u2.arglist = $4; $$->u3.macro_statements = $7;
232 globals : KW_GLOBALS LC global_statements RC {
233 $$ = npval2(PV_GLOBALS, &@1, &@4);
234 $$->u1.statements = $3;
238 global_statements : { $$ = NULL; }
239 | assignment global_statements {$$ = linku1($1, $2); }
240 | error global_statements {$$=$2;}
243 assignment : word EQ { reset_semicount(parseio->scanner); } word SEMI {
244 $$ = npval2(PV_VARDEC, &@1, &@5);
245 if (!ast_compat_app_set && !only_one_app_set_warning && strchr($4,'"')) {
246 ast_log(LOG_NOTICE,"Note: In asterisk.conf, in the [compat] section, the app_set is set to 1.6 or greater. The Set() function no longer removes double quotes from the value. If this is a surprise to you, you can set app_set to 1.4.\n");
247 only_one_app_set_warning = 1;
253 local_assignment : KW_LOCAL word EQ { reset_semicount(parseio->scanner); } word SEMI {
254 $$ = npval2(PV_LOCALVARDEC, &@1, &@6);
259 /* XXX this matches missing arguments, is this desired ? */
260 arglist : /* empty */ { $$ = NULL; }
261 | word { $$ = nword($1, &@1); }
262 | arglist COMMA word { $$ = linku1($1, nword($3, &@3)); }
263 | arglist error {$$=$1;}
267 | element elements { $$ = linku1($1, $2); }
268 | error elements { $$=$2;}
271 element : extension {$$=$1;}
276 | assignment {$$=$1;}
277 | local_assignment {$$=$1;}
278 | word error {free($1); $$=0;}
279 | SEMI {$$=0;/* allow older docs to be read */}
282 ignorepat : KW_IGNOREPAT EXTENMARK word SEMI {
283 $$ = npval2(PV_IGNOREPAT, &@1, &@4);
287 extension : word EXTENMARK statement {
288 $$ = npval2(PV_EXTENSION, &@1, &@3);
290 $$->u2.statements = $3; set_dads($$,$3);}
291 | word AT word EXTENMARK statement {
292 $$ = npval2(PV_EXTENSION, &@1, &@3);
293 $$->u1.str = malloc(strlen($1)+strlen($3)+2);
294 strcpy($$->u1.str,$1);
295 strcat($$->u1.str,"@");
296 strcat($$->u1.str,$3);
298 $$->u2.statements = $5; set_dads($$,$5);}
299 | KW_REGEXTEN word EXTENMARK statement {
300 $$ = npval2(PV_EXTENSION, &@1, &@4);
302 $$->u2.statements = $4; set_dads($$,$4);
304 | KW_HINT LP hint_word RP word EXTENMARK statement {
305 $$ = npval2(PV_EXTENSION, &@1, &@7);
307 $$->u2.statements = $7; set_dads($$,$7);
309 | KW_REGEXTEN KW_HINT LP hint_word RP word EXTENMARK statement {
310 $$ = npval2(PV_EXTENSION, &@1, &@8);
312 $$->u2.statements = $8; set_dads($$,$8);
317 /* list of statements in a block or after a case label - can be empty */
318 statements : /* empty */ { $$ = NULL; }
319 | statement statements { $$ = linku1($1, $2); }
320 | error statements {$$=$2;}
323 /* hh:mm-hh:mm, due to the way the parser works we do not
324 * detect the '-' but only the ':' as separator
326 timerange: word3_list COLON word3_list COLON word3_list {
327 asprintf(&$$, "%s:%s:%s", $1, $3, $5);
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 asprintf(&($$), "%s%s", $1, $2);
372 hint_word : word { $$ = $1; }
374 asprintf(&($$), "%s %s", $1, $2);
377 | hint_word COLON word {
378 asprintf(&($$), "%s:%s", $1, $3);
381 | hint_word AMPER word { /* there are often '&' in hints */
382 asprintf(&($$), "%s&%s", $1, $3);
387 word3_list : word { $$ = $1;}
389 asprintf(&($$), "%s%s", $1, $2);
394 asprintf(&($$), "%s%s%s", $1, $2, $3);
401 goto_word : word { $$ = $1;}
403 asprintf(&($$), "%s%s", $1, $2);
406 | goto_word COLON word {
407 asprintf(&($$), "%s:%s", $1, $3);
412 switch_statement : KW_SWITCH test_expr LC case_statements RC {
413 $$ = npval2(PV_SWITCH, &@1, &@5);
415 $$->u2.statements = $4; set_dads($$,$4);}
419 * Definition of a statememt in our language
421 statement : LC statements RC {
422 $$ = npval2(PV_STATEMENTBLOCK, &@1, &@3);
423 $$->u1.list = $2; set_dads($$,$2);}
424 | assignment { $$ = $1; }
425 | local_assignment { $$ = $1; }
426 | KW_GOTO target SEMI {
427 $$ = npval2(PV_GOTO, &@1, &@3);
429 | KW_JUMP jumptarget SEMI {
430 $$ = npval2(PV_GOTO, &@1, &@3);
433 $$ = npval2(PV_LABEL, &@1, &@2);
435 | KW_FOR LP {reset_semicount(parseio->scanner);} word SEMI
436 {reset_semicount(parseio->scanner);} word SEMI
437 {reset_parencount(parseio->scanner);} word RP statement { /* XXX word_list maybe ? */
438 $$ = npval2(PV_FOR, &@1, &@12);
439 $$->u1.for_init = $4;
441 $$->u3.for_inc = $10;
442 $$->u4.for_statements = $12; set_dads($$,$12);}
443 | KW_WHILE test_expr statement {
444 $$ = npval2(PV_WHILE, &@1, &@3);
446 $$->u2.statements = $3; set_dads($$,$3);}
447 | switch_statement { $$ = $1; }
448 | AMPER macro_call SEMI { $$ = update_last($2, &@2); }
449 | application_call SEMI { $$ = update_last($1, &@2); }
451 $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
453 | application_call EQ {reset_semicount(parseio->scanner);} word SEMI {
457 $$ = npval2(PV_VARDEC, &@1, &@5);
459 /* rebuild the original string-- this is not an app call, it's an unwrapped vardec, with a func call on the LHS */
460 /* string to big to fit in the buffer? */
461 tot+=strlen($1->u1.str);
462 for(pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
463 tot+=strlen(pptr->u1.str);
464 tot++; /* for a sep like a comma */
466 tot+=4; /* for safety */
467 bufx = calloc(1, tot);
468 strcpy(bufx,$1->u1.str);
470 /* XXX need to advance the pointer or the loop is very inefficient */
471 for (pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
472 if ( pptr != $1->u2.arglist )
474 strcat(bufx,pptr->u1.str);
478 if ( !ael_is_funcname($1->u1.str) )
479 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",
480 my_file, @1.first_line, @1.first_column, @1.last_column, $1->u1.str);
483 destroy_pval($1); /* the app call it is not, get rid of that chain */
486 | KW_BREAK SEMI { $$ = npval2(PV_BREAK, &@1, &@2); }
487 | KW_RETURN SEMI { $$ = npval2(PV_RETURN, &@1, &@2); }
488 | KW_CONTINUE SEMI { $$ = npval2(PV_CONTINUE, &@1, &@2); }
489 | if_like_head statement opt_else {
490 $$ = update_last($1, &@2);
491 $$->u2.statements = $2; set_dads($$,$2);
492 $$->u3.else_statements = $3;set_dads($$,$3);}
496 opt_else : KW_ELSE statement { $$ = $2; }
500 target : goto_word { $$ = nword($1, &@1); }
501 | goto_word BAR goto_word {
503 $$->next = nword($3, &@3); }
504 | goto_word COMMA goto_word {
506 $$->next = nword($3, &@3); }
507 | goto_word BAR goto_word BAR goto_word {
509 $$->next = nword($3, &@3);
510 $$->next->next = nword($5, &@5); }
511 | goto_word COMMA goto_word COMMA goto_word {
513 $$->next = nword($3, &@3);
514 $$->next->next = nword($5, &@5); }
515 | KW_DEFAULT BAR goto_word BAR goto_word {
516 $$ = nword(strdup("default"), &@1);
517 $$->next = nword($3, &@3);
518 $$->next->next = nword($5, &@5); }
519 | KW_DEFAULT COMMA goto_word COMMA goto_word {
520 $$ = nword(strdup("default"), &@1);
521 $$->next = nword($3, &@3);
522 $$->next->next = nword($5, &@5); }
525 opt_pri : /* empty */ { $$ = strdup("1"); }
526 | COMMA word { $$ = $2; }
529 /* XXX please document the form of jumptarget */
530 jumptarget : goto_word opt_pri { /* ext[, pri] default 1 */
532 $$->next = nword($2, &@2); } /* jump extension[,priority][@context] */
533 | goto_word opt_pri AT context_name { /* context, ext, pri */
535 $$->next = nword($1, &@1);
536 $$->next->next = nword($2, &@2); }
539 macro_call : word LP {reset_argcount(parseio->scanner);} eval_arglist RP {
540 /* XXX original code had @2 but i think we need @5 */
541 $$ = npval2(PV_MACRO_CALL, &@1, &@5);
543 $$->u2.arglist = $4;}
545 $$= npval2(PV_MACRO_CALL, &@1, &@3);
549 /* XXX application_call_head must be revised. Having 'word LP { ...'
550 * just as above should work fine, however it gives a different result.
552 application_call_head: word LP {reset_argcount(parseio->scanner);} {
553 if (strcasecmp($1,"goto") == 0) {
554 $$ = npval2(PV_GOTO, &@1, &@2);
555 free($1); /* won't be using this */
556 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 );
558 $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
563 application_call : application_call_head eval_arglist RP {
564 $$ = update_last($1, &@3);
565 if( $$->type == PV_GOTO )
570 | application_call_head RP { $$ = update_last($1, &@2); }
573 opt_word : word { $$ = $1 }
574 | { $$ = strdup(""); }
577 eval_arglist : word_list { $$ = nword($1, &@1); }
579 $$= npval(PV_WORD,0/*@1.first_line*/,0/*@1.last_line*/,0/* @1.first_column*/, 0/*@1.last_column*/);
580 $$->u1.str = strdup(""); }
581 | eval_arglist COMMA opt_word { $$ = linku1($1, nword($3, &@3)); }
584 case_statements: /* empty */ { $$ = NULL; }
585 | case_statement case_statements { $$ = linku1($1, $2); }
588 case_statement: KW_CASE word COLON statements {
589 $$ = npval2(PV_CASE, &@1, &@3); /* XXX 3 or 4 ? */
591 $$->u2.statements = $4; set_dads($$,$4);}
592 | KW_DEFAULT COLON statements {
593 $$ = npval2(PV_DEFAULT, &@1, &@3);
595 $$->u2.statements = $3;set_dads($$,$3);}
596 | KW_PATTERN word COLON statements {
597 $$ = npval2(PV_PATTERN, &@1, &@4); /* XXX@3 or @4 ? */
599 $$->u2.statements = $4;set_dads($$,$4);}
602 macro_statements: /* empty */ { $$ = NULL; }
603 | macro_statement macro_statements { $$ = linku1($1, $2); }
606 macro_statement : statement {$$=$1;}
608 | KW_CATCH word LC statements RC {
609 $$ = npval2(PV_CATCH, &@1, &@5);
611 $$->u2.statements = $4; set_dads($$,$4);}
614 switches : KW_SWITCHES LC switchlist RC {
615 $$ = npval2(PV_SWITCHES, &@1, &@2);
616 $$->u1.list = $3; set_dads($$,$3);}
619 eswitches : KW_ESWITCHES LC switchlist RC {
620 $$ = npval2(PV_ESWITCHES, &@1, &@2);
621 $$->u1.list = $3; set_dads($$,$3);}
624 switchlist : /* empty */ { $$ = NULL; }
625 | word SEMI switchlist { $$ = linku1(nword($1, &@1), $3); }
626 | word AT word SEMI switchlist { char *x; asprintf(&x,"%s@%s", $1,$3); free($1); free($3);
627 $$ = linku1(nword(x, &@1), $5);}
628 | error switchlist {$$=$2;}
631 included_entry : context_name { $$ = nword($1, &@1); }
632 | context_name BAR timespec {
635 prev_word=0; /* XXX sure ? */ }
638 /* list of ';' separated context names followed by optional timespec */
639 includeslist : included_entry SEMI { $$ = $1; }
640 | includeslist included_entry SEMI { $$ = linku1($1, $2); }
641 | includeslist error {$$=$1;}
644 includes : KW_INCLUDES LC includeslist RC {
645 $$ = npval2(PV_INCLUDES, &@1, &@4);
646 $$->u1.list = $3;set_dads($$,$3);}
647 | KW_INCLUDES LC RC {
648 $$ = npval2(PV_INCLUDES, &@1, &@3);}
654 static char *token_equivs1[] =
694 static char *token_equivs2[] =
735 static char *ael_token_subst(const char *mess)
737 /* calc a length, malloc, fill, and return; yyerror had better free it! */
741 int token_equivs_entries = sizeof(token_equivs1)/sizeof(char*);
743 for (p=mess; *p; p++) {
744 for (i=0; i<token_equivs_entries; i++) {
745 if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 )
747 len+=strlen(token_equivs2[i])+2;
748 p += strlen(token_equivs1[i])-1;
754 res = calloc(1, len+1);
759 for (i=0; i<token_equivs_entries; i++) {
760 if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) {
762 for (t=token_equivs2[i]; *t;) {
766 p += strlen(token_equivs1[i]);
778 void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s)
780 char *s2 = ael_token_subst((char *)s);
781 if (locp->first_line == locp->last_line) {
782 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);
784 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);
787 parseio->syntax_error_count++;
790 struct pval *npval(pvaltype type, int first_line, int last_line,
791 int first_column, int last_column)
793 pval *z = calloc(1, sizeof(struct pval));
795 z->startline = first_line;
796 z->endline = last_line;
797 z->startcol = first_column;
798 z->endcol = last_column;
799 z->filename = strdup(my_file);
803 static struct pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last)
805 return npval(type, first->first_line, last->last_line,
806 first->first_column, last->last_column);
809 static struct pval *update_last(pval *obj, YYLTYPE *last)
811 obj->endline = last->last_line;
812 obj->endcol = last->last_column;
816 /* frontend for npval to create a PV_WORD string from the given token */
817 static pval *nword(char *string, YYLTYPE *pos)
819 pval *p = npval2(PV_WORD, pos, pos);
825 /* this routine adds a dad ptr to each element in the list */
826 static void set_dads(struct pval *dad, struct pval *child_list)
830 for(t=child_list;t;t=t->next) /* simple stuff */