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