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/ael_structs.h"
36 pval * linku1(pval *head, pval *tail);
37 static void set_dads(pval *dad, pval *child_list);
38 void reset_parencount(yyscan_t yyscanner);
39 void reset_semicount(yyscan_t yyscanner);
40 void reset_argcount(yyscan_t yyscanner );
42 #define YYLEX_PARAM ((struct parse_io *)parseio)->scanner
43 #define YYERROR_VERBOSE 1
47 int ael_is_funcname(char *name);
49 static char *ael_token_subst(char *mess);
55 int intval; /* integer value, typically flags */
56 char *str; /* strings */
57 struct pval *pval; /* full objects */
61 /* declaring these AFTER the union makes things a lot simpler! */
62 void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s);
63 int ael_yylex (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , void * yyscanner);
65 /* create a new object with start-end marker */
66 pval *npval(pvaltype type, int first_line, int last_line,
67 int first_column, int last_column);
69 /* create a new object with start-end marker, simplified interface.
70 * Must be declared here because YYLTYPE is not known before
72 static pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last);
74 /* another frontend for npval, this time for a string */
75 static pval *nword(char *string, YYLTYPE *pos);
77 /* update end position of an object, return the object */
78 static pval *update_last(pval *, YYLTYPE *);
82 %token KW_CONTEXT LC RC LP RP SEMI EQ COMMA COLON AMPER BAR AT
83 %token KW_MACRO KW_GLOBALS KW_IGNOREPAT KW_SWITCH KW_IF KW_IFTIME KW_ELSE KW_RANDOM KW_ABSTRACT
84 %token EXTENMARK KW_GOTO KW_JUMP KW_RETURN KW_BREAK KW_CONTINUE KW_REGEXTEN KW_HINT
85 %token KW_FOR KW_WHILE KW_CASE KW_PATTERN KW_DEFAULT KW_CATCH KW_SWITCHES KW_ESWITCHES
86 %token KW_INCLUDES KW_LOCAL
93 %type <pval>includeslist
94 %type <pval>switchlist
97 %type <pval>macro_statement
98 %type <pval>macro_statements
99 %type <pval>case_statement
100 %type <pval>case_statements
101 %type <pval>eval_arglist
102 %type <pval>application_call
103 %type <pval>application_call_head
104 %type <pval>macro_call
105 %type <pval>target jumptarget
106 %type <pval>statement
107 %type <pval>switch_statement
109 %type <pval>if_like_head
110 %type <pval>statements
111 %type <pval>extension
112 %type <pval>ignorepat
116 %type <pval>assignment
117 %type <pval>local_assignment
118 %type <pval>global_statements
128 %type <pval>included_entry
131 %type <str>context_name
136 %type <str>word3_list hint_word
140 %type <intval>opt_abstract
146 %locations /* track source location using @n variables (yylloc in flex) */
147 %pure-parser /* pass yylval and yylloc as arguments to yylex(). */
148 %name-prefix="ael_yy"
150 * add an additional argument, parseio, to yyparse(),
151 * which is then accessible in the grammar actions
153 %parse-param {struct parse_io *parseio}
155 /* 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
156 the default action to shift will attach the else to the preceeding if. */
161 * declare destructors for objects.
162 * The former is for pval, the latter for strings.
163 * NOTE: we must not have a destructor for a 'file' object.
168 } includes includeslist switchlist eswitches switches
169 macro_statement macro_statements case_statement case_statements
170 eval_arglist application_call application_call_head
171 macro_call target jumptarget statement switch_statement
172 if_like_head statements extension
173 ignorepat element elements arglist assignment local_assignment
174 global_statements globals macro context object objects
176 timespec included_entry
178 %destructor { free($$);} word word_list goto_word word3_list opt_word context_name
186 file : objects { $$ = parseio->pval = $1; }
189 objects : object {$$=$1;}
190 | objects object { $$ = linku1($1, $2); }
191 | objects error {$$=$1;}
194 object : context {$$=$1;}
197 | SEMI {$$=0;/* allow older docs to be read */}
200 context_name : word { $$ = $1; }
201 | KW_DEFAULT { $$ = strdup("default"); }
204 context : opt_abstract KW_CONTEXT context_name LC elements RC {
206 ast_log(LOG_WARNING, "==== File: %s, Line %d, Cols: %d-%d: Warning! The empty context %s will be IGNORED!\n",
207 my_file, @4.first_line, @4.first_column, @4.last_column, $3 );
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; }
223 macro : KW_MACRO word LP arglist RP LC macro_statements RC {
224 $$ = npval2(PV_MACRO, &@1, &@8);
225 $$->u1.str = $2; $$->u2.arglist = $4; $$->u3.macro_statements = $7;
229 globals : KW_GLOBALS LC global_statements RC {
230 $$ = npval2(PV_GLOBALS, &@1, &@4);
231 $$->u1.statements = $3;
235 global_statements : { $$ = NULL; }
236 | assignment global_statements {$$ = linku1($1, $2); }
237 | global_statements error {$$=$1;}
240 assignment : word EQ { reset_semicount(parseio->scanner); } word SEMI {
241 $$ = npval2(PV_VARDEC, &@1, &@5);
246 local_assignment : KW_LOCAL word EQ { reset_semicount(parseio->scanner); } word SEMI {
247 $$ = npval2(PV_LOCALVARDEC, &@1, &@6);
252 /* XXX this matches missing arguments, is this desired ? */
253 arglist : /* empty */ { $$ = NULL; }
254 | word { $$ = nword($1, &@1); }
255 | arglist COMMA word { $$ = linku1($1, nword($3, &@3)); }
256 | arglist error {$$=$1;}
260 | element elements { $$ = linku1($1, $2); }
261 | elements error { $$=$1;}
264 element : extension {$$=$1;}
269 | assignment {$$=$1;}
270 | local_assignment {$$=$1;}
271 | word error {free($1); $$=0;}
272 | SEMI {$$=0;/* allow older docs to be read */}
275 ignorepat : KW_IGNOREPAT EXTENMARK word SEMI {
276 $$ = npval2(PV_IGNOREPAT, &@1, &@4);
280 extension : word EXTENMARK statement {
281 $$ = npval2(PV_EXTENSION, &@1, &@3);
283 $$->u2.statements = $3; set_dads($$,$3);}
284 | KW_REGEXTEN word EXTENMARK statement {
285 $$ = npval2(PV_EXTENSION, &@1, &@4);
287 $$->u2.statements = $4; set_dads($$,$4);
289 | KW_HINT LP hint_word RP word EXTENMARK statement {
290 $$ = npval2(PV_EXTENSION, &@1, &@7);
292 $$->u2.statements = $7; set_dads($$,$7);
294 | KW_REGEXTEN KW_HINT LP hint_word RP word EXTENMARK statement {
295 $$ = npval2(PV_EXTENSION, &@1, &@8);
297 $$->u2.statements = $8; set_dads($$,$8);
303 /* list of statements in a block or after a case label - can be empty */
304 statements : /* empty */ { $$ = NULL; }
305 | statement statements { $$ = linku1($1, $2); }
306 | statements error {$$=$1;}
309 /* hh:mm-hh:mm, due to the way the parser works we do not
310 * detect the '-' but only the ':' as separator
312 timerange: word3_list COLON word3_list COLON word3_list {
313 asprintf(&$$, "%s:%s:%s", $1, $3, $5);
320 /* full time specification range|dow|*|* */
321 timespec : timerange BAR word3_list BAR word3_list BAR word3_list {
323 $$->next = nword($3, &@3);
324 $$->next->next = nword($5, &@5);
325 $$->next->next->next = nword($7, &@7); }
328 /* expression used in if, random, while, switch */
329 test_expr : LP { reset_parencount(parseio->scanner); } word_list RP { $$ = $3; }
332 /* 'if' like statements: if, iftime, random */
333 if_like_head : KW_IF test_expr {
334 $$= npval2(PV_IF, &@1, &@2);
336 | KW_RANDOM test_expr {
337 $$ = npval2(PV_RANDOM, &@1, &@2);
339 | KW_IFTIME LP timespec RP {
340 $$ = npval2(PV_IFTIME, &@1, &@4);
345 /* word_list is a hack to fix a problem with context switching between bison and flex;
346 by the time you register a new context with flex, you've already got a look-ahead token
347 from the old context, with no way to put it back and start afresh. So, we kludge this
348 and merge the words back together. */
350 word_list : word { $$ = $1;}
352 asprintf(&($$), "%s%s", $1, $2);
358 hint_word : word { $$ = $1; }
360 asprintf(&($$), "%s %s", $1, $2);
363 | hint_word AMPER word { /* there are often '&' in hints */
364 asprintf(&($$), "%s&%s", $1, $3);
369 word3_list : word { $$ = $1;}
371 asprintf(&($$), "%s%s", $1, $2);
376 asprintf(&($$), "%s%s%s", $1, $2, $3);
383 goto_word : word { $$ = $1;}
385 asprintf(&($$), "%s%s", $1, $2);
388 | goto_word COLON word {
389 asprintf(&($$), "%s:%s", $1, $3);
394 switch_statement : KW_SWITCH test_expr LC case_statements RC {
395 $$ = npval2(PV_SWITCH, &@1, &@5);
397 $$->u2.statements = $4; set_dads($$,$4);}
401 * Definition of a statememt in our language
403 statement : LC statements RC {
404 $$ = npval2(PV_STATEMENTBLOCK, &@1, &@3);
405 $$->u1.list = $2; set_dads($$,$2);}
406 | assignment { $$ = $1; }
407 | local_assignment { $$ = $1; }
408 | KW_GOTO target SEMI {
409 $$ = npval2(PV_GOTO, &@1, &@3);
411 | KW_JUMP jumptarget SEMI {
412 $$ = npval2(PV_GOTO, &@1, &@3);
415 $$ = npval2(PV_LABEL, &@1, &@2);
417 | KW_FOR LP {reset_semicount(parseio->scanner);} word SEMI
418 {reset_semicount(parseio->scanner);} word SEMI
419 {reset_parencount(parseio->scanner);} word RP statement { /* XXX word_list maybe ? */
420 $$ = npval2(PV_FOR, &@1, &@12);
421 $$->u1.for_init = $4;
423 $$->u3.for_inc = $10;
424 $$->u4.for_statements = $12; set_dads($$,$12);}
425 | KW_WHILE test_expr statement {
426 $$ = npval2(PV_WHILE, &@1, &@3);
428 $$->u2.statements = $3; set_dads($$,$3);}
429 | switch_statement { $$ = $1; }
430 | AMPER macro_call SEMI { $$ = update_last($2, &@2); }
431 | application_call SEMI { $$ = update_last($1, &@2); }
433 $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
435 | application_call EQ {reset_semicount(parseio->scanner);} word SEMI {
439 $$ = npval2(PV_VARDEC, &@1, &@5);
441 /* rebuild the original string-- this is not an app call, it's an unwrapped vardec, with a func call on the LHS */
442 /* string to big to fit in the buffer? */
443 tot+=strlen($1->u1.str);
444 for(pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
445 tot+=strlen(pptr->u1.str);
446 tot++; /* for a sep like a comma */
448 tot+=4; /* for safety */
449 bufx = calloc(1, tot);
450 strcpy(bufx,$1->u1.str);
452 /* XXX need to advance the pointer or the loop is very inefficient */
453 for (pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
454 if ( pptr != $1->u2.arglist )
456 strcat(bufx,pptr->u1.str);
460 if ( !ael_is_funcname($1->u1.str) )
461 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",
462 my_file, @1.first_line, @1.first_column, @1.last_column, $1->u1.str);
465 destroy_pval($1); /* the app call it is not, get rid of that chain */
468 | KW_BREAK SEMI { $$ = npval2(PV_BREAK, &@1, &@2); }
469 | KW_RETURN SEMI { $$ = npval2(PV_RETURN, &@1, &@2); }
470 | KW_CONTINUE SEMI { $$ = npval2(PV_CONTINUE, &@1, &@2); }
471 | if_like_head statement opt_else {
472 $$ = update_last($1, &@2);
473 $$->u2.statements = $2; set_dads($$,$2);
474 $$->u3.else_statements = $3;set_dads($$,$3);}
478 opt_else : KW_ELSE statement { $$ = $2; }
482 target : goto_word { $$ = nword($1, &@1); }
483 | goto_word BAR goto_word {
485 $$->next = nword($3, &@3); }
486 | goto_word COMMA goto_word {
488 $$->next = nword($3, &@3); }
489 | goto_word BAR goto_word BAR goto_word {
491 $$->next = nword($3, &@3);
492 $$->next->next = nword($5, &@5); }
493 | goto_word COMMA goto_word COMMA goto_word {
495 $$->next = nword($3, &@3);
496 $$->next->next = nword($5, &@5); }
497 | KW_DEFAULT BAR goto_word BAR goto_word {
498 $$ = nword(strdup("default"), &@1);
499 $$->next = nword($3, &@3);
500 $$->next->next = nword($5, &@5); }
501 | KW_DEFAULT COMMA goto_word COMMA goto_word {
502 $$ = nword(strdup("default"), &@1);
503 $$->next = nword($3, &@3);
504 $$->next->next = nword($5, &@5); }
507 opt_pri : /* empty */ { $$ = strdup("1"); }
508 | COMMA word { $$ = $2; }
511 /* XXX please document the form of jumptarget */
512 jumptarget : goto_word opt_pri { /* ext[, pri] default 1 */
514 $$->next = nword($2, &@2); } /* jump extension[,priority][@context] */
515 | goto_word opt_pri AT context_name { /* context, ext, pri */
517 $$->next = nword($1, &@1);
518 $$->next->next = nword($2, &@2); }
521 macro_call : word LP {reset_argcount(parseio->scanner);} eval_arglist RP {
522 /* XXX original code had @2 but i think we need @5 */
523 $$ = npval2(PV_MACRO_CALL, &@1, &@5);
525 $$->u2.arglist = $4;}
527 $$= npval2(PV_MACRO_CALL, &@1, &@3);
531 /* XXX application_call_head must be revised. Having 'word LP { ...'
532 * just as above should work fine, however it gives a different result.
534 application_call_head: word LP {reset_argcount(parseio->scanner);} {
535 if (strcasecmp($1,"goto") == 0) {
536 $$ = npval2(PV_GOTO, &@1, &@2);
537 free($1); /* won't be using this */
538 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 );
540 $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
545 application_call : application_call_head eval_arglist RP {
546 $$ = update_last($1, &@3);
547 if( $$->type == PV_GOTO )
552 | application_call_head RP { $$ = update_last($1, &@2); }
555 opt_word : word { $$ = $1 }
556 | { $$ = strdup(""); }
559 eval_arglist : word_list { $$ = nword($1, &@1); }
561 $$= npval(PV_WORD,0/*@1.first_line*/,0/*@1.last_line*/,0/* @1.first_column*/, 0/*@1.last_column*/);
562 $$->u1.str = strdup(""); }
563 | eval_arglist COMMA opt_word { $$ = linku1($1, nword($3, &@3)); }
566 case_statements: /* empty */ { $$ = NULL; }
567 | case_statement case_statements { $$ = linku1($1, $2); }
570 case_statement: KW_CASE word COLON statements {
571 $$ = npval2(PV_CASE, &@1, &@3); /* XXX 3 or 4 ? */
573 $$->u2.statements = $4; set_dads($$,$4);}
574 | KW_DEFAULT COLON statements {
575 $$ = npval2(PV_DEFAULT, &@1, &@3);
577 $$->u2.statements = $3;set_dads($$,$3);}
578 | KW_PATTERN word COLON statements {
579 $$ = npval2(PV_PATTERN, &@1, &@4); /* XXX@3 or @4 ? */
581 $$->u2.statements = $4;set_dads($$,$4);}
584 macro_statements: /* empty */ { $$ = NULL; }
585 | macro_statement macro_statements { $$ = linku1($1, $2); }
588 macro_statement : statement {$$=$1;}
590 | KW_CATCH word LC statements RC {
591 $$ = npval2(PV_CATCH, &@1, &@5);
593 $$->u2.statements = $4; set_dads($$,$4);}
596 switches : KW_SWITCHES LC switchlist RC {
597 $$ = npval2(PV_SWITCHES, &@1, &@2);
598 $$->u1.list = $3; set_dads($$,$3);}
601 eswitches : KW_ESWITCHES LC switchlist RC {
602 $$ = npval2(PV_ESWITCHES, &@1, &@2);
603 $$->u1.list = $3; set_dads($$,$3);}
606 switchlist : /* empty */ { $$ = NULL; }
607 | word SEMI switchlist { $$ = linku1(nword($1, &@1), $3); }
608 | word AT word SEMI switchlist { char *x; asprintf(&x,"%s@%s", $1,$3); free($1); free($3);
609 $$ = linku1(nword(x, &@1), $5);}
610 | switchlist error {$$=$1;}
613 included_entry : context_name { $$ = nword($1, &@1); }
614 | context_name BAR timespec {
617 prev_word=0; /* XXX sure ? */ }
620 /* list of ';' separated context names followed by optional timespec */
621 includeslist : included_entry SEMI { $$ = $1; }
622 | includeslist included_entry SEMI { $$ = linku1($1, $2); }
623 | includeslist error {$$=$1;}
626 includes : KW_INCLUDES LC includeslist RC {
627 $$ = npval2(PV_INCLUDES, &@1, &@4);
628 $$->u1.list = $3;set_dads($$,$3);}
629 | KW_INCLUDES LC RC {
630 $$ = npval2(PV_INCLUDES, &@1, &@3);}
636 static char *token_equivs1[] =
676 static char *token_equivs2[] =
717 static char *ael_token_subst(char *mess)
719 /* calc a length, malloc, fill, and return; yyerror had better free it! */
723 int token_equivs_entries = sizeof(token_equivs1)/sizeof(char*);
725 for (p=mess; *p; p++) {
726 for (i=0; i<token_equivs_entries; i++) {
727 if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 )
729 len+=strlen(token_equivs2[i])+2;
730 p += strlen(token_equivs1[i])-1;
736 res = calloc(1, len+1);
741 for (i=0; i<token_equivs_entries; i++) {
742 if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) {
744 for (t=token_equivs2[i]; *t;) {
748 p += strlen(token_equivs1[i]);
760 void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s)
762 char *s2 = ael_token_subst((char *)s);
763 if (locp->first_line == locp->last_line) {
764 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);
766 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);
769 parseio->syntax_error_count++;
772 struct pval *npval(pvaltype type, int first_line, int last_line,
773 int first_column, int last_column)
775 pval *z = calloc(1, sizeof(struct pval));
777 z->startline = first_line;
778 z->endline = last_line;
779 z->startcol = first_column;
780 z->endcol = last_column;
781 z->filename = strdup(my_file);
785 static struct pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last)
787 return npval(type, first->first_line, last->last_line,
788 first->first_column, last->last_column);
791 static struct pval *update_last(pval *obj, YYLTYPE *last)
793 obj->endline = last->last_line;
794 obj->endcol = last->last_column;
798 /* frontend for npval to create a PV_WORD string from the given token */
799 static pval *nword(char *string, YYLTYPE *pos)
801 pval *p = npval2(PV_WORD, pos, pos);
807 /* this routine adds a dad ptr to each element in the list */
808 static void set_dads(struct pval *dad, struct pval *child_list)
812 for(t=child_list;t;t=t->next) /* simple stuff */