macro statements can be empty
[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         ;
212
213 globals : KW_GLOBALS LC global_statements RC {
214                 $$ = npval2(PV_GLOBALS, &@1, &@4);
215                 $$->u1.statements = $3;}
216         | KW_GLOBALS LC RC { /* empty globals is OK */
217                 $$ = npval2(PV_GLOBALS, &@1, &@3); }
218         ;
219
220 global_statements : global_statement {$$=$1;}
221         | global_statements global_statement {$$ = linku1($1, $2); }
222         | global_statements error {$$=$1;}
223         ;
224
225 global_statement : word EQ { reset_semicount(parseio->scanner); }  word SEMI {
226                 $$ = npval2(PV_VARDEC, &@1, &@5);
227                 $$->u1.str = $1;
228                 $$->u2.val = $4; }
229         ;
230
231 arglist : /* empty */ { $$ = NULL; }
232         | word { $$ = nword($1, &@1); }
233         | arglist COMMA word { $$ = linku1($1, nword($3, &@3)); }
234         | arglist error {$$=$1;}
235         ;
236
237 elements_block : LC RC  { $$ = NULL; }
238         | LC elements RC { $$ = $2; }
239         ;
240
241 elements : element { $$=$1;}
242         | error {$$=0;}
243         | elements element { $$ = linku1($1, $2); }
244         | elements error   { $$=$1;}
245         ;
246
247 element : extension {$$=$1;}
248         | includes {$$=$1;}
249         | switches {$$=$1;}
250         | eswitches {$$=$1;}
251         | ignorepat {$$=$1;}
252         | word EQ { reset_semicount(parseio->scanner); } word SEMI {
253                 $$ = npval2(PV_VARDEC, &@1, &@5);
254                 $$->u1.str = $1;
255                 $$->u2.val = $4; }
256         | word error {free($1); $$=0;}
257         | SEMI  {$$=0;/* allow older docs to be read */}
258         ;
259
260 ignorepat : KW_IGNOREPAT EXTENMARK word SEMI {
261                 $$ = npval2(PV_IGNOREPAT, &@1, &@4);
262                 $$->u1.str = $3;}
263         ;
264
265 extension : word EXTENMARK statement {
266                 $$ = npval2(PV_EXTENSION, &@1, &@3);
267                 $$->u1.str = $1;
268                 $$->u2.statements = $3; }
269         | KW_REGEXTEN word EXTENMARK statement {
270                 $$ = npval2(PV_EXTENSION, &@1, &@4);
271                 $$->u1.str = $2;
272                 $$->u2.statements = $4;
273                 $$->u4.regexten=1;}
274         | KW_HINT LP word3_list RP word EXTENMARK statement {
275                 $$ = npval2(PV_EXTENSION, &@1, &@7);
276                 $$->u1.str = $5;
277                 $$->u2.statements = $7;
278                 $$->u3.hints = $3;}
279         | KW_REGEXTEN KW_HINT LP word3_list RP word EXTENMARK statement {
280                 $$ = npval2(PV_EXTENSION, &@1, &@8);
281                 $$->u1.str = $6;
282                 $$->u2.statements = $8;
283                 $$->u4.regexten=1;
284                 $$->u3.hints = $4;}
285
286         ;
287
288 statements : statement {$$=$1;}
289         | statements statement { $$ = linku1($1, $2); }
290         | statements error {$$=$1;}
291         ;
292
293 /* hh:mm-hh:mm, due to the way the parser works we do not
294  * detect the '-' but only the ':' as separator
295  */
296 timerange: word3_list COLON word3_list COLON word3_list {
297                 asprintf(&$$, "%s:%s:%s", $1, $3, $5);
298                 free($1);
299                 free($3);
300                 free($5); }
301         | word { $$ = $1; }
302         ;
303
304 /* full time specification range|dow|*|* */
305 timespec : timerange BAR word3_list BAR word3_list BAR word3_list {
306                 $$ = nword($1, &@1);
307                 $$->u1.list = nword($3, &@3);
308                 $$->u1.list->next = nword($5, &@5);
309                 $$->u1.list->next->next = nword($7, &@7); }
310         ;
311
312 /* 'if' like statements: if, iftime, random */
313 if_like_head : KW_IF LP { reset_parencount(parseio->scanner); }  word_list RP {
314                 $$= npval2(PV_IF, &@1, &@5);
315                 $$->u1.str = $4; }
316         |  KW_RANDOM LP { reset_parencount(parseio->scanner); } word_list RP {
317                 $$ = npval2(PV_RANDOM, &@1, &@5);
318                 $$->u1.str=$4;}
319         | KW_IFTIME LP timespec RP {
320                 $$ = npval2(PV_IFTIME, &@1, &@4);
321                 $$->u1.list = $3;
322                 prev_word = 0; }
323         ;
324
325 /* word_list is a hack to fix a problem with context switching between bison and flex;
326    by the time you register a new context with flex, you've already got a look-ahead token
327    from the old context, with no way to put it back and start afresh. So, we kludge this
328    and merge the words back together. */
329
330 word_list : word { $$ = $1;}
331         | word word {
332                 asprintf(&($$), "%s%s", $1, $2);
333                 free($1);
334                 free($2);
335                 prev_word = $$;}
336         ;
337
338 word3_list : word { $$ = $1;}
339         | word word {
340                 asprintf(&($$), "%s%s", $1, $2);
341                 free($1);
342                 free($2);
343                 prev_word = $$;}
344         | word word word {
345                 asprintf(&($$), "%s%s%s", $1, $2, $3);
346                 free($1);
347                 free($2);
348                 free($3);
349                 prev_word=$$;}
350         ;
351
352 goto_word : word { $$ = $1;}
353         | word word {
354                 asprintf(&($$), "%s%s", $1, $2);
355                 free($1);
356                 free($2);}
357         | word COLON word {
358                 asprintf(&($$), "%s:%s", $1, $3);
359                 free($1);
360                 free($3);}
361         ;
362
363 switch_head : KW_SWITCH LP { reset_parencount(parseio->scanner); } word RP  LC {
364                 $$ = npval2(PV_SWITCH, &@1, &@6);
365                 $$->u1.str = $4; }
366         ;
367
368 /*
369  * Definition of a statememt in our language
370  */
371 statement : LC statements RC {
372                 $$ = npval2(PV_STATEMENTBLOCK, &@1, &@3);
373                 $$->u1.list = $2; }
374         | word EQ {reset_semicount(parseio->scanner);} word SEMI {
375                 $$ = npval2(PV_VARDEC, &@1, &@5);
376                 $$->u1.str = $1;
377                 $$->u2.val = $4; }
378         | KW_GOTO target SEMI {
379                 $$ = npval2(PV_GOTO, &@1, &@3);
380                 $$->u1.list = $2;}
381         | KW_JUMP jumptarget SEMI {
382                 $$ = npval2(PV_GOTO, &@1, &@3);
383                 $$->u1.list = $2;}
384         | word COLON {
385                 $$ = npval2(PV_LABEL, &@1, &@2);
386                 $$->u1.str = $1; }
387         | KW_FOR LP {reset_semicount(parseio->scanner);} word SEMI
388                         {reset_semicount(parseio->scanner);} word SEMI
389                         {reset_parencount(parseio->scanner);} word RP statement {
390                 $$ = npval2(PV_FOR, &@1, &@12);
391                 $$->u1.for_init = $4;
392                 $$->u2.for_test=$7;
393                 $$->u3.for_inc = $10;
394                 $$->u4.for_statements = $12;}
395         | KW_WHILE LP {reset_parencount(parseio->scanner);} word RP statement {
396                 $$ = npval2(PV_WHILE, &@1, &@6);
397                 $$->u1.str = $4;
398                 $$->u2.statements = $6; }
399         | switch_head RC /* empty list OK */ {
400                 $$ = update_last($1, &@2); }
401         | switch_head case_statements RC {
402                 $$ = update_last($1, &@3);
403                 $$->u2.statements = $2;}
404         | AMPER macro_call SEMI {
405                 $$ = update_last($2, &@2); }
406         | application_call SEMI {
407                 $$ = update_last($1, &@2); }
408         | word SEMI {
409                 $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
410                 $$->u1.str = $1;}
411         | application_call EQ {reset_semicount(parseio->scanner);} word SEMI {
412                 char *bufx;
413                 int tot=0;
414                 pval *pptr;
415                 $$ = npval2(PV_VARDEC, &@1, &@5);
416                 $$->u2.val=$4;
417                 /* rebuild the original string-- this is not an app call, it's an unwrapped vardec, with a func call on the LHS */
418                 /* string to big to fit in the buffer? */
419                 tot+=strlen($1->u1.str);
420                 for(pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
421                         tot+=strlen(pptr->u1.str);
422                         tot++; /* for a sep like a comma */
423                 }
424                 tot+=4; /* for safety */
425                 bufx = calloc(1, tot);
426                 strcpy(bufx,$1->u1.str);
427                 strcat(bufx,"(");
428                 /* XXX need to advance the pointer or the loop is very inefficient */
429                 for (pptr=$1->u2.arglist;pptr;pptr=pptr->next) {
430                         if ( pptr != $1->u2.arglist )
431                                 strcat(bufx,",");
432                         strcat(bufx,pptr->u1.str);
433                 }
434                 strcat(bufx,")");
435 #ifdef AAL_ARGCHECK
436                 if ( !ael_is_funcname($1->u1.str) )
437                         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",
438                                 my_file, @1.first_line, @1.first_column, @1.last_column, $1->u1.str);
439 #endif
440                 $$->u1.str = bufx;
441                 destroy_pval($1); /* the app call it is not, get rid of that chain */
442                 prev_word = 0;
443         }
444         | KW_BREAK SEMI { $$ = npval2(PV_BREAK, &@1, &@2); }
445         | KW_RETURN SEMI { $$ = npval2(PV_RETURN, &@1, &@2); }
446         | KW_CONTINUE SEMI { $$ = npval2(PV_CONTINUE, &@1, &@2); }
447         | if_like_head statement opt_else {
448                 $$ = update_last($1, &@2);
449                 $$->u2.statements = $2;
450                 $$->u3.else_statements = $3;}
451         | SEMI { $$=0; }
452         ;
453
454 opt_else : KW_ELSE statement { $$ = $2; }
455         | { $$ = NULL ; }
456
457 /* XXX unused */
458 bar_or_comma: BAR | COMMA ;
459
460 target : goto_word { $$ = nword($1, &@1); }
461         | goto_word BAR goto_word {
462                 $$ = nword($1, &@1);
463                 $$->next = nword($3, &@3); }
464         | goto_word COMMA goto_word {
465                 $$ = nword($1, &@1);
466                 $$->next = nword($3, &@3); }
467         | goto_word BAR goto_word BAR goto_word {
468                 $$ = nword($1, &@1);
469                 $$->next = nword($3, &@3);
470                 $$->next->next = nword($5, &@5); }
471         | goto_word COMMA goto_word COMMA goto_word {
472                 $$ = nword($1, &@1);
473                 $$->next = nword($3, &@3);
474                 $$->next->next = nword($5, &@5); }
475         | KW_DEFAULT BAR goto_word BAR goto_word {
476                 $$ = nword(strdup("default"), &@1);
477                 $$->next = nword($3, &@3);
478                 $$->next->next = nword($5, &@5); }
479         | KW_DEFAULT COMMA goto_word COMMA goto_word {
480                 $$ = nword(strdup("default"), &@1);
481                 $$->next = nword($3, &@3);
482                 $$->next->next = nword($5, &@5); }
483         ;
484
485 /* XXX please document the form of jumptarget */
486 jumptarget : goto_word {
487                 $$ = nword($1, &@1);
488                 $$->next = nword(strdup("1"), &@1); }  /*  jump extension[,priority][@context] */
489         | goto_word COMMA goto_word {
490                 $$ = nword($1, &@1);
491                 $$->next = nword($3, &@3); }
492         | goto_word COMMA word AT word {        /* XXX they are stored in a different order */
493                 $$ = nword($5, &@5);
494                 $$->next = nword($1, &@1);
495                 $$->next->next = nword($3, &@3); }
496         | goto_word AT goto_word {
497                 $$ = nword($3, &@3);
498                 $$->next = nword($1, &@1);
499                 $$->next->next = nword(strdup("1"), &@3); }
500         | goto_word COMMA word AT KW_DEFAULT {
501                 $$ = nword(strdup("default"), &@1);
502                 $$->next = nword($1, &@1);
503                 $$->next->next = nword($3, &@3); }
504         | goto_word AT KW_DEFAULT {
505                 $$ = nword(strdup("default"), &@1);
506                 $$->next = nword($1, &@3);
507                 $$->next->next = nword( strdup("1"), &@3); }
508         ;
509
510 macro_call : word LP {reset_argcount(parseio->scanner);} eval_arglist RP {
511                 /* XXX original code had @2 but i think we need @5 */
512                 $$ = npval2(PV_MACRO_CALL, &@1, &@5);
513                 $$->u1.str = $1;
514                 $$->u2.arglist = $4;}
515         | word LP RP {
516                 $$= npval2(PV_MACRO_CALL, &@1, &@3);
517                 $$->u1.str = $1; }
518         ;
519
520 /* XXX application_call_head must be revised. Having 'word LP { ...'
521  * just as above should work fine, however it gives a different result.
522  */
523 application_call_head: word LP {reset_argcount(parseio->scanner);} {
524                 if (strcasecmp($1,"goto") == 0) {
525                         $$ = npval2(PV_GOTO, &@1, &@2);
526                         free($1); /* won't be using this */
527                         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 );
528                 } else {
529                         $$= npval2(PV_APPLICATION_CALL, &@1, &@2);
530                         $$->u1.str = $1;
531                 } }
532         ;
533
534 application_call : application_call_head eval_arglist RP {
535                 $$ = update_last($1, &@3);
536                 if( $$->type == PV_GOTO )
537                         $$->u1.list = $2;
538                 else
539                         $$->u2.arglist = $2;
540         }
541         | application_call_head RP { $$ = update_last($1, &@2); }
542         ;
543
544 opt_word : word { $$ = $1 }
545         | { $$ = strdup(""); }
546         ;
547
548 eval_arglist :  word_list { $$ = nword($1, &@1); }
549         | /*nothing! */   {
550                 $$= npval(PV_WORD,0/*@1.first_line*/,0/*@1.last_line*/,0/* @1.first_column*/, 0/*@1.last_column*/);
551                 $$->u1.str = strdup(""); }
552         | eval_arglist COMMA  opt_word { $$ = linku1($1, nword($3, &@3)); }
553         ;
554
555 case_statements: case_statement {$$=$1;}
556         | case_statements case_statement { $$ = linku1($1, $2); }
557         ;
558
559 case_statement: KW_CASE word COLON statements {
560                 $$ = npval2(PV_CASE, &@1, &@3); /* XXX 3 or 4 ? */
561                 $$->u1.str = $2;
562                 $$->u2.statements = $4;}
563         | KW_DEFAULT COLON statements {
564                 $$ = npval2(PV_DEFAULT, &@1, &@3);
565                 $$->u1.str = NULL;
566                 $$->u2.statements = $3;}
567         | KW_PATTERN word COLON statements {
568                 $$ = npval2(PV_PATTERN, &@1, &@4); /* XXX@3 or @4 ? */
569                 $$->u1.str = $2;
570                 $$->u2.statements = $4;}
571         | KW_CASE word COLON {
572                 $$ = npval2(PV_CASE, &@1, &@3);
573                 $$->u1.str = $2;}
574         | KW_DEFAULT COLON {
575                 $$ = npval2(PV_DEFAULT, &@1, &@2);
576                 $$->u1.str = NULL;}
577         | KW_PATTERN word COLON  {
578                 $$ = npval2(PV_PATTERN, &@1, &@3);
579                 $$->u1.str = $2;}
580         ;
581
582 macro_statements: /* empty */ { $$ = NULL; }
583         | macro_statement {$$ = $1;}
584         | macro_statements macro_statement { $$ = linku1($1, $2); }
585         ;
586
587 macro_statement : statement {$$=$1;}
588         | KW_CATCH word LC statements RC {
589                 $$ = npval2(PV_CATCH, &@1, &@5);
590                 $$->u1.str = $2;
591                 $$->u2.statements = $4;}
592         ;
593
594 switches : KW_SWITCHES switchlist_block {
595                 $$ = npval2(PV_SWITCHES, &@1, &@2);
596                 $$->u1.list = $2; }
597         ;
598
599 eswitches : KW_ESWITCHES switchlist_block {
600                 $$ = npval2(PV_ESWITCHES, &@1, &@2);
601                 $$->u1.list = $2; }
602         ;
603
604 switchlist_block : LC switchlist RC { $$ = $2; }
605         | LC RC { $$ = NULL; }
606         ;
607
608 switchlist : word SEMI { $$ = nword($1, &@1); }
609         | switchlist word SEMI { $$ = linku1($1, nword($2, &@2)); }
610         | switchlist error {$$=$1;}
611         ;
612
613 included_entry : context_name SEMI { $$ = nword($1, &@1); }
614         | context_name BAR timespec SEMI {
615                 $$ = nword($1, &@1);
616                 $$->u2.arglist = $3;
617                 prev_word=0; /* XXX sure ? */ }
618         ;
619
620 /* list of ';' separated context names followed by optional timespec */
621 includeslist : included_entry { $$ = $1; }
622         | includeslist included_entry { $$ = linku1($1, $2); }
623         | includeslist error {$$=$1;}
624         ;
625
626 includes : KW_INCLUDES LC includeslist RC {
627                 $$ = npval2(PV_INCLUDES, &@1, &@4);
628                 $$->u1.list = $3;}
629         | KW_INCLUDES LC RC {
630                 $$ = npval2(PV_INCLUDES, &@1, &@3);}
631         ;
632
633
634 %%
635
636 static char *token_equivs1[] =
637 {
638         "AMPER",
639         "AT",
640         "BAR",
641         "COLON",
642         "COMMA",
643         "EQ",
644         "EXTENMARK",
645         "KW_BREAK",
646         "KW_CASE",
647         "KW_CATCH",
648         "KW_CONTEXT",
649         "KW_CONTINUE",
650         "KW_DEFAULT",
651         "KW_ELSE",
652         "KW_ESWITCHES",
653         "KW_FOR",
654         "KW_GLOBALS",
655         "KW_GOTO",
656         "KW_HINT",
657         "KW_IFTIME",
658         "KW_IF",
659         "KW_IGNOREPAT",
660         "KW_INCLUDES"
661         "KW_JUMP",
662         "KW_MACRO",
663         "KW_PATTERN",
664         "KW_REGEXTEN",
665         "KW_RETURN",
666         "KW_SWITCHES",
667         "KW_SWITCH",
668         "KW_WHILE",
669         "LC",
670         "LP",
671         "RC",
672         "RP",
673         "SEMI",
674 };
675
676 static char *token_equivs2[] =
677 {
678         "&",
679         "@",
680         "|",
681         ":",
682         ",",
683         "=",
684         "=>",
685         "break",
686         "case",
687         "catch",
688         "context",
689         "continue",
690         "default",
691         "else",
692         "eswitches",
693         "for",
694         "globals",
695         "goto",
696         "hint",
697         "ifTime",
698         "if",
699         "ignorepat",
700         "includes"
701         "jump",
702         "macro",
703         "pattern",
704         "regexten",
705         "return",
706         "switches",
707         "switch",
708         "while",
709         "{",
710         "(",
711         "}",
712         ")",
713         ";",
714 };
715
716
717 static char *ael_token_subst(char *mess)
718 {
719         /* calc a length, malloc, fill, and return; yyerror had better free it! */
720         int len=0,i;
721         char *p;
722         char *res, *s,*t;
723         int token_equivs_entries = sizeof(token_equivs1)/sizeof(char*);
724
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 )
728                         {
729                                 len+=strlen(token_equivs2[i])+2;
730                                 p += strlen(token_equivs1[i])-1;
731                                 break;
732                         }
733                 }
734                 len++;
735         }
736         res = calloc(1, len+1);
737         res[0] = 0;
738         s = res;
739         for (p=mess; *p;) {
740                 int found = 0;
741                 for (i=0; i<token_equivs_entries; i++) {
742                         if ( strncmp(p,token_equivs1[i],strlen(token_equivs1[i])) == 0 ) {
743                                 *s++ = '\'';
744                                 for (t=token_equivs2[i]; *t;) {
745                                         *s++ = *t++;
746                                 }
747                                 *s++ = '\'';
748                                 p += strlen(token_equivs1[i]);
749                                 found = 1;
750                                 break;
751                         }
752                 }
753                 if( !found )
754                         *s++ = *p++;
755         }
756         *s++ = 0;
757         return res;
758 }
759
760 void yyerror(YYLTYPE *locp, struct parse_io *parseio,  char const *s)
761 {
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);
765         } else {
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);
767         }
768         free(s2);
769         parseio->syntax_error_count++;
770 }
771
772 static struct pval *npval(pvaltype type, int first_line, int last_line,
773         int first_column, int last_column)
774 {
775         pval *z = calloc(1, sizeof(struct pval));
776         z->type = type;
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);
782         return z;
783 }
784
785 static struct pval *npval2(pvaltype type, YYLTYPE *first, YYLTYPE *last)
786 {
787         return npval(type, first->first_line, last->last_line,
788                         first->first_column, last->last_column);
789 }
790
791 static struct pval *update_last(pval *obj, YYLTYPE *last)
792 {
793         obj->endline = last->last_line;
794         obj->endcol = last->last_column;
795         return obj;
796 }
797
798 /* frontend for npval to create a PV_WORD string from the given token */
799 static pval *nword(char *string, YYLTYPE *pos)
800 {
801         pval *p = npval2(PV_WORD, pos, pos);
802         if (p)
803                 p->u1.str = string;
804         return p;
805 }
806
807 /* append second element to the list in the first one */
808 static pval * linku1(pval *head, pval *tail)
809 {
810         if (!head)
811                 return tail;
812         if (tail) {
813                 if (!head->next) {
814                         head->next = tail;
815                 } else {
816                         head->u1_last->next = tail;
817                 }
818                 head->u1_last = tail;
819         }
820         return head;
821 }