18e20484c1f84abb37abb5c0756fc64c532d26dd
[asterisk/asterisk.git] / pbx / ael / ael.y
1 %{
2 /*
3  * Asterisk -- An open source telephony toolkit.
4  *
5  * Copyright (C) 2006, Digium, Inc.
6  *
7  * Steve Murphy <murf@parsetree.com>
8  *
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.
14  *
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.
18  */
19 /*! \file
20  *
21  * \brief Bison Grammar description of AEL2.
22  *
23  */
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include "asterisk/logger.h"
28 #include "asterisk/ael_structs.h"
29
30 static pval * linku1(pval *head, pval *tail);
31
32 void reset_parencount(yyscan_t yyscanner);
33 void reset_semicount(yyscan_t yyscanner);
34 void reset_argcount(yyscan_t yyscanner );
35
36 #define YYLEX_PARAM ((struct parse_io *)parseio)->scanner
37 #define YYERROR_VERBOSE 1
38
39 extern char *my_file;
40 #ifdef AAL_ARGCHECK
41 int ael_is_funcname(char *name);
42 #endif
43 static char *ael_token_subst(char *mess);
44
45 %}
46
47
48 %union {
49         int     intval;         /* integer value, typically flags */
50         char    *str;           /* strings */
51         struct pval *pval;      /* full objects */
52 }
53
54 %{
55         /* declaring these AFTER the union makes things a lot simpler! */
56 void yyerror(YYLTYPE *locp, struct parse_io *parseio, char const *s);
57 int ael_yylex (YYSTYPE * yylval_param, YYLTYPE * yylloc_param , void * yyscanner);
58
59 /* create a new object with start-end marker */
60 static pval *npval(pvaltype type, int first_line, int last_line,
61         int first_column, int last_column);
62
63 /* create a new object with start-end marker, simplified interface.
64  * Must be declared here because YYLTYPE is not known before
65  */
66 static pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last);
67
68 /* another frontend for npval, this time for a string */
69 static pval *nword(char *string, YYLTYPE *pos);
70
71 /* update end position of an object, return the object */
72 static pval *update_last(pval *, YYLTYPE *);
73 %}
74
75
76 %token KW_CONTEXT LC RC LP RP SEMI EQ COMMA COLON AMPER BAR AT
77 %token KW_MACRO KW_GLOBALS KW_IGNOREPAT KW_SWITCH KW_IF KW_IFTIME KW_ELSE KW_RANDOM KW_ABSTRACT
78 %token EXTENMARK KW_GOTO KW_JUMP KW_RETURN KW_BREAK KW_CONTINUE KW_REGEXTEN KW_HINT
79 %token KW_FOR KW_WHILE KW_CASE KW_PATTERN KW_DEFAULT KW_CATCH KW_SWITCHES KW_ESWITCHES
80 %token KW_INCLUDES
81
82 %right BAR COMMA
83
84 %token <str> word
85
86 %type <pval>includes
87 %type <pval>includeslist
88 %type <pval>switchlist
89 %type <pval>eswitches
90 %type <pval>switches
91 %type <pval>macro_statement
92 %type <pval>macro_statements
93 %type <pval>case_statement
94 %type <pval>case_statements
95 %type <pval>eval_arglist
96 %type <pval>application_call
97 %type <pval>application_call_head
98 %type <pval>macro_call
99 %type <pval>target jumptarget
100 %type <pval>statement
101 %type <pval>switch_head
102
103 %type <pval>if_like_head
104 %type <pval>statements
105 %type <pval>extension
106 %type <pval>ignorepat
107 %type <pval>element
108 %type <pval>elements
109 %type <pval>arglist
110 %type <pval>global_statement
111 %type <pval>global_statements
112 %type <pval>globals
113 %type <pval>macro
114 %type <pval>context
115 %type <pval>object
116 %type <pval>objects
117 %type <pval>file
118 /* XXX lr changes */
119 %type <pval>opt_else
120 %type <pval>elements_block
121 %type <pval>switchlist_block
122 %type <pval>timespec
123 %type <pval>included_entry
124
125 %type <str>opt_word
126 %type <str>context_name
127 %type <str>timerange
128
129 %type <str>goto_word
130 %type <str>word_list
131 %type <str>word3_list
132
133 %type <intval>opt_abstract
134
135 /*
136  * OPTIONS
137  */
138
139 %locations      /* track source location using @n variables (yylloc in flex) */
140 %pure-parser    /* pass yylval and yylloc as arguments to yylex(). */
141 %name-prefix="ael_yy"
142 /*
143  * add an additional argument, parseio, to yyparse(),
144  * which is then accessible in the grammar actions
145  */
146 %parse-param {struct parse_io *parseio}
147
148 /* 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
149    the default action to shift will attach the else to the preceeding if. */
150 %expect 5
151 %error-verbose
152
153 /*
154  * declare destructors for objects.
155  * The former is for pval, the latter for strings.
156  * NOTE: we must not have a destructor for a 'file' object.
157  */
158 %destructor {
159                 destroy_pval($$);
160                 prev_word=0;
161         }       includes includeslist switchlist eswitches switches
162                 macro_statement macro_statements case_statement case_statements
163                 eval_arglist application_call application_call_head
164                 macro_call target jumptarget statement switch_head
165                 if_like_head statements extension
166                 ignorepat element elements arglist global_statement
167                 global_statements globals macro context object objects
168                 opt_else
169                 elements_block switchlist_block
170                 timespec included_entry
171
172 %destructor { free($$);}  word word_list goto_word word3_list opt_word context_name
173                 timerange
174
175
176 %%
177
178 file : objects  { $$ = parseio->pval = $1; }
179         ;
180
181 objects : object {$$=$1;}
182         | objects object { $$ = linku1($1, $2); }
183         | objects error {$$=$1;}
184         ;
185
186 object : context {$$=$1;}
187         | macro {$$=$1;}
188         | globals {$$=$1;}
189         | SEMI  {$$=0;/* allow older docs to be read */}
190         ;
191
192 context_name : word { $$ = $1; }
193         | KW_DEFAULT { $$ = strdup("default"); }
194         ;
195
196 context : opt_abstract KW_CONTEXT context_name elements_block {
197                 $$ = npval2(PV_CONTEXT, &@1, &@4);
198                 $$->u1.str = $3;
199                 $$->u2.statements = $4;
200                 $$->u3.abstract = $1; }
201         ;
202
203 /* optional "abstract" keyword  XXX there is no regression test for this */
204 opt_abstract: KW_ABSTRACT { $$ = 1; }
205         | /* nothing */ { $$ = 0; }
206         ;
207
208 macro : KW_MACRO word LP arglist RP LC macro_statements RC {
209                 $$ = npval2(PV_MACRO, &@1, &@8);
210                 $$->u1.str = $2; $$->u2.arglist = $4; $$->u3.macro_statements = $7; }
211         | KW_MACRO word LP arglist RP LC  RC {
212                 $$ = npval2(PV_MACRO, &@1, &@7);
213                 $$->u1.str = $2; $$->u2.arglist = $4; }
214         | KW_MACRO word LP RP LC macro_statements RC {
215                 $$ = npval2(PV_MACRO, &@1, &@7);
216                 $$->u1.str = $2;
217                 $$->u3.macro_statements = $6; }
218         | KW_MACRO word LP RP LC  RC {
219                 $$ = npval2(PV_MACRO, &@1, &@6);
220                 $$->u1.str = $2; }
221         ;
222
223 globals : KW_GLOBALS LC global_statements RC {
224                 $$ = npval2(PV_GLOBALS, &@1, &@4);
225                 $$->u1.statements = $3;}
226         | KW_GLOBALS LC RC { /* empty globals is OK */
227                 $$ = npval2(PV_GLOBALS, &@1, &@3); }
228         ;
229
230 global_statements : global_statement {$$=$1;}
231         | global_statements global_statement {$$ = linku1($1, $2); }
232         | global_statements error {$$=$1;}
233         ;
234
235 global_statement : word EQ { reset_semicount(parseio->scanner); }  word SEMI {
236                 $$ = npval2(PV_VARDEC, &@1, &@5);
237                 $$->u1.str = $1;
238                 $$->u2.val = $4; }
239         ;
240
241 arglist : word { $$ = nword($1, &@1); }
242         | arglist COMMA word { $$ = linku1($1, nword($3, &@3)); }
243         | arglist error {$$=$1;}
244         ;
245
246 elements_block : LC RC  { $$ = NULL; }
247         | LC elements RC { $$ = $2; }
248         ;
249
250 elements : element { $$=$1;}
251         | error {$$=0;}
252         | elements element { $$ = linku1($1, $2); }
253         | elements error   { $$=$1;}
254         ;
255
256 element : extension {$$=$1;}
257         | includes {$$=$1;}
258         | switches {$$=$1;}
259         | eswitches {$$=$1;}
260         | ignorepat {$$=$1;}
261         | word EQ { reset_semicount(parseio->scanner); } word SEMI {
262                 $$ = npval2(PV_VARDEC, &@1, &@5);
263                 $$->u1.str = $1;
264                 $$->u2.val = $4; }
265         | word error {free($1); $$=0;}
266         | SEMI  {$$=0;/* allow older docs to be read */}
267         ;
268
269 ignorepat : KW_IGNOREPAT EXTENMARK word SEMI {
270                 $$ = npval2(PV_IGNOREPAT, &@1, &@4);
271                 $$->u1.str = $3;}
272         ;
273
274 extension : word EXTENMARK statement {
275                 $$ = npval2(PV_EXTENSION, &@1, &@3);
276                 $$->u1.str = $1;
277                 $$->u2.statements = $3; }
278         | KW_REGEXTEN word EXTENMARK statement {
279                 $$ = npval2(PV_EXTENSION, &@1, &@4);
280                 $$->u1.str = $2;
281                 $$->u2.statements = $4;
282                 $$->u4.regexten=1;}
283         | KW_HINT LP word3_list RP word EXTENMARK statement {
284                 $$ = npval2(PV_EXTENSION, &@1, &@7);
285                 $$->u1.str = $5;
286                 $$->u2.statements = $7;
287                 $$->u3.hints = $3;}
288         | KW_REGEXTEN KW_HINT LP word3_list RP word EXTENMARK statement {
289                 $$ = npval2(PV_EXTENSION, &@1, &@8);
290                 $$->u1.str = $6;
291                 $$->u2.statements = $8;
292                 $$->u4.regexten=1;
293                 $$->u3.hints = $4;}
294
295         ;
296
297 statements : statement {$$=$1;}
298         | statements statement { $$ = linku1($1, $2); }
299         | statements error {$$=$1;}
300         ;
301
302 /* hh:mm-hh:mm, due to the way the parser works we do not
303  * detect the '-' but only the ':' as separator
304  */
305 timerange: word3_list COLON word3_list COLON word3_list {
306                 asprintf(&$$, "%s:%s:%s", $1, $3, $5);
307                 free($1);
308                 free($3);
309                 free($5); }
310         | word { $$ = $1; }
311         ;
312
313 /* full time specification range|dow|*|* */
314 timespec : timerange BAR word3_list BAR word3_list BAR word3_list {
315                 $$ = nword($1, &@1);
316                 $$->u1.list = nword($3, &@3);
317                 $$->u1.list->next = nword($5, &@5);
318                 $$->u1.list->next->next = nword($7, &@7); }
319         ;
320
321 /* 'if' like statements: if, iftime, random */
322 if_like_head : KW_IF LP { reset_parencount(parseio->scanner); }  word_list RP {
323                 $$= npval2(PV_IF, &@1, &@5);
324                 $$->u1.str = $4; }
325         |  KW_RANDOM LP { reset_parencount(parseio->scanner); } word_list RP {
326                 $$ = npval2(PV_RANDOM, &@1, &@5);
327                 $$->u1.str=$4;}
328         | KW_IFTIME LP timespec RP {
329                 $$ = npval2(PV_IFTIME, &@1, &@4);
330                 $$->u1.list = $3;
331                 prev_word = 0; }
332         ;
333
334 /* word_list is a hack to fix a problem with context switching between bison and flex;
335    by the time you register a new context with flex, you've already got a look-ahead token
336    from the old context, with no way to put it back and start afresh. So, we kludge this
337    and merge the words back together. */
338
339 word_list : word { $$ = $1;}
340         | word word {
341                 asprintf(&($$), "%s%s", $1, $2);
342                 free($1);
343                 free($2);
344                 prev_word = $$;}
345         ;
346
347 word3_list : word { $$ = $1;}
348         | word word {
349                 asprintf(&($$), "%s%s", $1, $2);
350                 free($1);
351                 free($2);
352                 prev_word = $$;}
353         | word word word {
354                 asprintf(&($$), "%s%s%s", $1, $2, $3);
355                 free($1);
356                 free($2);
357                 free($3);
358                 prev_word=$$;}
359         ;
360
361 goto_word : word { $$ = $1;}
362         | word word {
363                 asprintf(&($$), "%s%s", $1, $2);
364                 free($1);
365                 free($2);}
366         | word COLON word {
367                 asprintf(&($$), "%s:%s", $1, $3);
368                 free($1);
369                 free($3);}
370         ;
371
372 switch_head : KW_SWITCH LP { reset_parencount(parseio->scanner); } word RP  LC {
373                 $$ = npval2(PV_SWITCH, &@1, &@6);
374                 $$->u1.str = $4; }
375         ;
376
377 /*
378  * Definition of a statememt in our language
379  */
380 statement : LC statements RC {
381                 $$ = npval2(PV_STATEMENTBLOCK, &@1, &@3);
382                 $$->u1.list = $2; }
383         | word EQ {reset_semicount(parseio->scanner);} word SEMI {
384                 $$ = npval2(PV_VARDEC, &@1, &@5);
385                 $$->u1.str = $1;
386                 $$->u2.val = $4; }
387         | KW_GOTO target SEMI {
388                 $$ = npval2(PV_GOTO, &@1, &@3);
389                 $$->u1.list = $2;}
390         | KW_JUMP jumptarget SEMI {
391                 $$ = npval2(PV_GOTO, &@1, &@3);
392                 $$->u1.list = $2;}
393         | word COLON {
394                 $$ = npval2(PV_LABEL, &@1, &@2);
395                 $$->u1.str = $1; }
396         | KW_FOR LP {reset_semicount(parseio->scanner);} word SEMI
397                         {reset_semicount(parseio->scanner);} word SEMI
398                         {reset_parencount(parseio->scanner);} word RP statement {
399                 $$ = npval2(PV_FOR, &@1, &@12);
400                 $$->u1.for_init = $4;
401                 $$->u2.for_test=$7;
402                 $$->u3.for_inc = $10;
403                 $$->u4.for_statements = $12;}
404         | KW_WHILE LP {reset_parencount(parseio->scanner);} word RP statement {
405                 $$ = npval2(PV_WHILE, &@1, &@6);
406                 $$->u1.str = $4;
407                 $$->u2.statements = $6; }
408         | switch_head RC /* empty list OK */ {
409                 $$ = update_last($1, &@2); }
410         | switch_head case_statements RC {
411                 $$ = update_last($1, &@3);
412                 $$->u2.statements = $2;}
413         | AMPER macro_call SEMI {
414                 $$ = update_last($2, &@2); }
415         | application_call SEMI {
416                 $$ = update_last($1, &@2); }
417         | word SEMI {
418                 $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
419                 $$->u1.str = $1;}
420         | application_call EQ {reset_semicount(parseio->scanner);} word SEMI {
421                 char *bufx;
422                 int tot=0;
423                 pval *pptr;
424                 $$ = npval2(PV_VARDEC, &@1, &@5);
425                 $$->u2.val=$4;
426                 /* rebuild the original string-- this is not an app call, it's an unwrapped vardec, with a func call on the LHS */
427                 /* string to big to fit in the buffer? */
428                 tot+=strlen($1->u1.str);
429                 for(pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
430                         tot+=strlen(pptr->u1.str);
431                         tot++; /* for a sep like a comma */
432                 }
433                 tot+=4; /* for safety */
434                 bufx = calloc(1, tot);
435                 strcpy(bufx,$1->u1.str);
436                 strcat(bufx,"(");
437                 /* XXX need to advance the pointer or the loop is very inefficient */
438                 for (pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
439                         if ( pptr != $1->u2.arglist )
440                                 strcat(bufx,",");
441                         strcat(bufx,pptr->u1.str);
442                 }
443                 strcat(bufx,")");
444 #ifdef AAL_ARGCHECK
445                 if ( !ael_is_funcname($1->u1.str) )
446                         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",
447                                 my_file, @1.first_line, @1.first_column, @1.last_column, $1->u1.str);
448 #endif
449                 $$->u1.str = bufx;
450                 destroy_pval($1); /* the app call it is not, get rid of that chain */
451                 prev_word = 0;
452         }
453         | KW_BREAK SEMI { $$ = npval2(PV_BREAK, &@1, &@2); }
454         | KW_RETURN SEMI { $$ = npval2(PV_RETURN, &@1, &@2); }
455         | KW_CONTINUE SEMI { $$ = npval2(PV_CONTINUE, &@1, &@2); }
456         | if_like_head statement opt_else {
457                 $$ = update_last($1, &@2);
458                 $$->u2.statements = $2;
459                 $$->u3.else_statements = $3;}
460         | SEMI { $$=0; }
461         ;
462
463 opt_else : KW_ELSE statement { $$ = $2; }
464         | { $$ = NULL ; }
465
466 /* XXX unused */
467 bar_or_comma: BAR | COMMA ;
468
469 target : goto_word { $$ = nword($1, &@1); }
470         | goto_word BAR goto_word {
471                 $$ = nword($1, &@1);
472                 $$->next = nword($3, &@3); }
473         | goto_word COMMA goto_word {
474                 $$ = nword($1, &@1);
475                 $$->next = nword($3, &@3); }
476         | goto_word BAR goto_word BAR goto_word {
477                 $$ = nword($1, &@1);
478                 $$->next = nword($3, &@3);
479                 $$->next->next = nword($5, &@5); }
480         | goto_word COMMA goto_word COMMA goto_word {
481                 $$ = nword($1, &@1);
482                 $$->next = nword($3, &@3);
483                 $$->next->next = nword($5, &@5); }
484         | KW_DEFAULT BAR goto_word BAR goto_word {
485                 $$ = nword(strdup("default"), &@1);
486                 $$->next = nword($3, &@3);
487                 $$->next->next = nword($5, &@5); }
488         | KW_DEFAULT COMMA goto_word COMMA goto_word {
489                 $$ = nword(strdup("default"), &@1);
490                 $$->next = nword($3, &@3);
491                 $$->next->next = nword($5, &@5); }
492         ;
493
494 /* XXX please document the form of jumptarget */
495 jumptarget : goto_word {
496                 $$ = nword($1, &@1);
497                 $$->next = nword(strdup("1"), &@1); }  /*  jump extension[,priority][@context] */
498         | goto_word COMMA goto_word {
499                 $$ = nword($1, &@1);
500                 $$->next = nword($3, &@3); }
501         | goto_word COMMA word AT word {        /* XXX they are stored in a different order */
502                 $$ = nword($5, &@5);
503                 $$->next = nword($1, &@1);
504                 $$->next->next = nword($3, &@3); }
505         | goto_word AT goto_word {
506                 $$ = nword($3, &@3);
507                 $$->next = nword($1, &@1);
508                 $$->next->next = nword(strdup("1"), &@3); }
509         | goto_word COMMA word AT KW_DEFAULT {
510                 $$ = nword(strdup("default"), &@1);
511                 $$->next = nword($1, &@1);
512                 $$->next->next = nword($3, &@3); }
513         | goto_word AT KW_DEFAULT {
514                 $$ = nword(strdup("default"), &@1);
515                 $$->next = nword($1, &@3);
516                 $$->next->next = nword( strdup("1"), &@3); }
517         ;
518
519 macro_call : word LP {reset_argcount(parseio->scanner);} eval_arglist RP {
520                 /* XXX original code had @2 but i think we need @5 */
521                 $$ = npval2(PV_MACRO_CALL, &@1, &@5);
522                 $$->u1.str = $1;
523                 $$->u2.arglist = $4;}
524         | word LP RP {
525                 $$= npval2(PV_MACRO_CALL, &@1, &@3);
526                 $$->u1.str = $1; }
527         ;
528
529 /* XXX application_call_head must be revised. Having 'word LP { ...'
530  * just as above should work fine, however it gives a different result.
531  */
532 application_call_head: word LP {reset_argcount(parseio->scanner);} {
533                 if (strcasecmp($1,"goto") == 0) {
534                         $$ = npval2(PV_GOTO, &@1, &@2);
535                         free($1); /* won't be using this */
536                         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 );
537                 } else {
538                         $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
539                         $$->u1.str = $1;
540                 } }
541         ;
542
543 application_call : application_call_head eval_arglist RP {
544                 $$ = update_last($1, &@3);
545                 if( $$->type == PV_GOTO )
546                         $$->u1.list = $2;
547                 else
548                         $$->u2.arglist = $2;
549         }
550         | application_call_head RP { $$ = update_last($1, &@2); }
551         ;
552
553 opt_word : word { $$ = $1 }
554         | { $$ = strdup(""); }
555         ;
556
557 eval_arglist :  word_list { $$ = nword($1, &@1); }
558         | /*nothing! */   {
559                 $$= npval(PV_WORD,0/*@1.first_line*/,0/*@1.last_line*/,0/* @1.first_column*/, 0/*@1.last_column*/);
560                 $$->u1.str = strdup(""); }
561         | eval_arglist COMMA  opt_word { $$ = linku1($1, nword($3, &@3)); }
562         ;
563
564 case_statements: case_statement {$$=$1;}
565         | case_statements case_statement { $$ = linku1($1, $2); }
566         ;
567
568 case_statement: KW_CASE word COLON statements {
569                 $$ = npval2(PV_CASE, &@1, &@3); /* XXX 3 or 4 ? */
570                 $$->u1.str = $2;
571                 $$->u2.statements = $4;}
572         | KW_DEFAULT COLON statements {
573                 $$ = npval2(PV_DEFAULT, &@1, &@3);
574                 $$->u1.str = NULL;
575                 $$->u2.statements = $3;}
576         | KW_PATTERN word COLON statements {
577                 $$ = npval2(PV_PATTERN, &@1, &@4); /* XXX@3 or @4 ? */
578                 $$->u1.str = $2;
579                 $$->u2.statements = $4;}
580         | KW_CASE word COLON {
581                 $$ = npval2(PV_CASE, &@1, &@3);
582                 $$->u1.str = $2;}
583         | KW_DEFAULT COLON {
584                 $$ = npval2(PV_DEFAULT, &@1, &@2);
585                 $$->u1.str = NULL;}
586         | KW_PATTERN word COLON  {
587                 $$ = npval2(PV_PATTERN, &@1, &@3);
588                 $$->u1.str = $2;}
589         ;
590
591 macro_statements: macro_statement {$$ = $1;}
592         | macro_statements macro_statement { $$ = linku1($1, $2); }
593         ;
594
595 macro_statement : statement {$$=$1;}
596         | KW_CATCH word LC statements RC {
597                 $$ = npval2(PV_CATCH, &@1, &@5);
598                 $$->u1.str = $2;
599                 $$->u2.statements = $4;}
600         ;
601
602 switches : KW_SWITCHES switchlist_block {
603                 $$ = npval2(PV_SWITCHES, &@1, &@2);
604                 $$->u1.list = $2; }
605         ;
606
607 eswitches : KW_ESWITCHES switchlist_block {
608                 $$ = npval2(PV_ESWITCHES, &@1, &@2);
609                 $$->u1.list = $2; }
610         ;
611
612 switchlist_block : LC switchlist RC { $$ = $2; }
613         | LC RC { $$ = NULL; }
614         ;
615
616 switchlist : word SEMI { $$ = nword($1, &@1); }
617         | switchlist word SEMI { $$ = linku1($1, nword($2, &@2)); }
618         | switchlist error {$$=$1;}
619         ;
620
621 included_entry : context_name SEMI { $$ = nword($1, &@1); }
622         | context_name BAR timespec SEMI {
623                 $$ = nword($1, &@1);
624                 $$->u2.arglist = $3;
625                 prev_word=0; /* XXX sure ? */ }
626         ;
627
628 /* list of ';' separated context names followed by optional timespec */
629 includeslist : included_entry { $$ = $1; }
630         | includeslist included_entry { $$ = linku1($1, $2); }
631         | includeslist error {$$=$1;}
632         ;
633
634 includes : KW_INCLUDES LC includeslist RC {
635                 $$ = npval2(PV_INCLUDES, &@1, &@4);
636                 $$->u1.list = $3;}
637         | KW_INCLUDES LC RC {
638                 $$ = npval2(PV_INCLUDES, &@1, &@3);}
639         ;
640
641
642 %%
643
644 static char *token_equivs1[] =
645 {
646         "AMPER",
647         "AT",
648         "BAR",
649         "COLON",
650         "COMMA",
651         "EQ",
652         "EXTENMARK",
653         "KW_BREAK",
654         "KW_CASE",
655         "KW_CATCH",
656         "KW_CONTEXT",
657         "KW_CONTINUE",
658         "KW_DEFAULT",
659         "KW_ELSE",
660         "KW_ESWITCHES",
661         "KW_FOR",
662         "KW_GLOBALS",
663         "KW_GOTO",
664         "KW_HINT",
665         "KW_IFTIME",
666         "KW_IF",
667         "KW_IGNOREPAT",
668         "KW_INCLUDES"
669         "KW_JUMP",
670         "KW_MACRO",
671         "KW_PATTERN",
672         "KW_REGEXTEN",
673         "KW_RETURN",
674         "KW_SWITCHES",
675         "KW_SWITCH",
676         "KW_WHILE",
677         "LC",
678         "LP",
679         "RC",
680         "RP",
681         "SEMI",
682 };
683
684 static char *token_equivs2[] =
685 {
686         "&",
687         "@",
688         "|",
689         ":",
690         ",",
691         "=",
692         "=>",
693         "break",
694         "case",
695         "catch",
696         "context",
697         "continue",
698         "default",
699         "else",
700         "eswitches",
701         "for",
702         "globals",
703         "goto",
704         "hint",
705         "ifTime",
706         "if",
707         "ignorepat",
708         "includes"
709         "jump",
710         "macro",
711         "pattern",
712         "regexten",
713         "return",
714         "switches",
715         "switch",
716         "while",
717         "{",
718         "(",
719         "}",
720         ")",
721         ";",
722 };
723
724
725 static char *ael_token_subst(char *mess)
726 {
727         /* calc a length, malloc, fill, and return; yyerror had better free it! */
728         int len=0,i;
729         char *p;
730         char *res, *s,*t;
731         int token_equivs_entries = sizeof(token_equivs1)/sizeof(char*);
732
733         for (p=mess; *p; p++) {
734                 for (i=0; i<token_equivs_entries; i++) {
735                         if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 )
736                         {
737                                 len+=strlen(token_equivs2[i])+2;
738                                 p += strlen(token_equivs1[i])-1;
739                                 break;
740                         }
741                 }
742                 len++;
743         }
744         res = calloc(1, len+1);
745         res[0] = 0;
746         s = res;
747         for (p=mess; *p;) {
748                 int found = 0;
749                 for (i=0; i<token_equivs_entries; i++) {
750                         if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) {
751                                 *s++ = '\'';
752                                 for (t=token_equivs2[i]; *t;) {
753                                         *s++ = *t++;
754                                 }
755                                 *s++ = '\'';
756                                 p += strlen(token_equivs1[i]);
757                                 found = 1;
758                                 break;
759                         }
760                 }
761                 if( !found )
762                         *s++ = *p++;
763         }
764         *s++ = 0;
765         return res;
766 }
767
768 void yyerror(YYLTYPE *locp, struct parse_io *parseio,  char const *s)
769 {
770         char *s2 = ael_token_subst((char *)s);
771         if (locp->first_line == locp->last_line) {
772                 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);
773         } else {
774                 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);
775         }
776         free(s2);
777         parseio->syntax_error_count++;
778 }
779
780 static struct pval *npval(pvaltype type, int first_line, int last_line,
781         int first_column, int last_column)
782 {
783         pval *z = calloc(1, sizeof(struct pval));
784         z->type = type;
785         z->startline = first_line;
786         z->endline = last_line;
787         z->startcol = first_column;
788         z->endcol = last_column;
789         z->filename = strdup(my_file);
790         return z;
791 }
792
793 static struct pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last)
794 {
795         return npval(type, first->first_line, last->last_line,
796                         first->first_column, last->last_column);
797 }
798
799 static struct pval *update_last(pval *obj, YYLTYPE *last)
800 {
801         obj->endline = last->last_line;
802         obj->endcol = last->last_column;
803         return obj;
804 }
805
806 /* frontend for npval to create a PV_WORD string from the given token */
807 static pval *nword(char *string, YYLTYPE *pos)
808 {
809         pval *p = npval2(PV_WORD, pos, pos);
810         if (p)
811                 p->u1.str = string;
812         return p;
813 }
814
815 /* append second element to the list in the first one */
816 static pval * linku1(pval *head, pval *tail)
817 {
818         if (!head)
819                 return tail;
820         if (tail) {
821                 if (!head->next) {
822                         head->next = tail;
823                 } else {
824                         head->u1_last->next = tail;
825                 }
826                 head->u1_last = tail;
827         }
828         return head;
829 }