add upgraded expression parser (bug #2058)
authorKevin P. Fleming <kpfleming@digium.com>
Mon, 16 May 2005 00:35:38 +0000 (00:35 +0000)
committerKevin P. Fleming <kpfleming@digium.com>
Mon, 16 May 2005 00:35:38 +0000 (00:35 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@5691 65c4cc65-6c06-0410-ace0-fbb531ad65f3

Makefile
ast_expr2.fl [new file with mode: 0755]
ast_expr2.y [new file with mode: 0755]
doc/README.variables
vercomp.sh [new file with mode: 0755]

index 80f9fdc..6cd52af 100755 (executable)
--- a/Makefile
+++ b/Makefile
@@ -258,10 +258,21 @@ ifeq (${OSARCH},SunOS)
 LIBS+=-lpthread -ldl -lnsl -lsocket -lresolv -L$(CROSS_COMPILE_TARGET)/usr/local/ssl/lib
 endif
 LIBS+=-lssl
+
+FLEXVER_GT_2_5_31=$(shell ./vercomp.sh flex \>= 2.5.31)
+BISONVER=$(shell bison --version | grep \^bison | egrep -o '[0-9]+\.[-0-9.]+[a-z]?' )
+BISONVERGE_85=$(shell ./vercomp.sh bison \>= 1.85 )
+
+ifeq (${FLEXVER_GT_2_5_31},true)
+FLEXOBJS=ast_expr2.o ast_expr2f.o
+else
+FLEXOBJS=ast_expr.o
+endif
+
 OBJS=io.o sched.o logger.o frame.o loader.o config.o channel.o \
        translate.o file.o say.o pbx.o cli.o md5.o term.o \
        ulaw.o alaw.o callerid.o fskmodem.o image.o app.o \
-       cdr.o tdd.o acl.o rtp.o manager.o asterisk.o ast_expr.o \
+       cdr.o tdd.o acl.o rtp.o manager.o asterisk.o ${FLEXOBJS}  \
        dsp.o chanvars.o indications.o autoservice.o db.o privacy.o \
        astmm.o enum.o srv.o dns.o aescrypt.o aestab.o aeskey.o \
        utils.o config_old.o plc.o jitterbuf.o dnsmgr.o
@@ -333,14 +344,46 @@ _version:
 .version: _version
 
 .y.c:
-       bison $< --name-prefix=ast_yy -o $@
+       @if (($(BISONVERGE_85) = false)); then \
+               echo ================================================================================= ;\
+               echo NOTE: you may have trouble if you do not have bison-1.85 or higher installed! ;\
+               echo NOTE: you can pick up a copy at: http://ftp.gnu.org/ or its mirrors ;\
+               echo NOTE: You Have: $(BISONVER) ;\
+               echo ================================================================================; \
+       else \
+               echo EXCELLENT-- You have Bison version $(BISONVER), this should work just fine...;\
+       fi
+       bison -v -d --name-prefix=ast_yy $< -o $@
 
 ast_expr.o: ast_expr.c
+       @echo NOTE:
+       @echo NOTE:
+       @echo NOTE: Using older version of ast_expr. To use the newer version,
+       @echo NOTE: Upgrade to flex 2.5.31 or higher, which can be found at http://
+       @echo NOTE:  http://sourceforge.net/project/showfiles.php?group_id=72099
+       @echo NOTE:
+       @echo NOTE:
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) ast_expr.c
+
+ast_expr2.o: ast_expr2.c
+
+ast_expr2f.o: ast_expr2f.c
+ast_expr2f.c: ast_expr2.fl
+       flex ast_expr2.fl
 
 cli.o: cli.c build.h
 
 asterisk.o: asterisk.c build.h
 
+testexpr2 :
+       flex ast_expr2.fl
+       bison -v -d --name-prefix=ast_yy -o ast_expr2.c ast_expr2.y
+       gcc -g -c -DSTANDALONE ast_expr2f.c
+       gcc -g -c -DSTANDALONE ast_expr2.c
+       gcc -g -o testexpr2 ast_expr2f.o ast_expr2.o
+       rm ast_expr2.c ast_expr2.o ast_expr2f.o ast_expr2f.c
+
+
 manpage: asterisk.8.gz
 
 asterisk.8.gz: asterisk.sgml
diff --git a/ast_expr2.fl b/ast_expr2.fl
new file mode 100755 (executable)
index 0000000..fc56994
--- /dev/null
@@ -0,0 +1,167 @@
+%{
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <regex.h>
+#include <limits.h>
+#include <asterisk/ast_expr.h>
+#include <asterisk/logger.h>
+
+enum valtype {
+       AST_EXPR_integer, AST_EXPR_numeric_string, AST_EXPR_string
+} ;
+
+struct val {
+       enum valtype type;
+       union {
+               char *s;
+               quad_t i;
+       } u;
+} ;
+
+#include "ast_expr2.h" /* the o/p of the bison on ast_expr2.y */
+
+#define SET_COLUMNS yylloc_param->first_column = (int)(yyg->yytext_r - YY_CURRENT_BUFFER_LVALUE->yy_ch_buf);yylloc_param->last_column = yylloc_param->last_column + yyleng - 1; yylloc_param->first_line = yylloc_param->last_line = 1
+#define SET_STRING yylval_param->val = (struct val *)calloc(sizeof(struct val),1); yylval_param->val->type = AST_EXPR_string; yylval_param->val->u.s = strdup(yytext);
+
+struct parse_io
+{
+       char *string;
+       struct val *val;
+       yyscan_t scanner;
+};
+
+%}
+
+%option prefix="ast_yy"
+%option batch
+%option outfile="ast_expr2f.c"
+%option reentrant
+%option bison-bridge
+%option bison-locations
+%option noyywrap
+
+%%
+
+\|     { SET_COLUMNS; SET_STRING; return TOK_OR;}
+\&     { SET_COLUMNS; SET_STRING; return TOK_AND;}
+\=     { SET_COLUMNS; SET_STRING; return TOK_EQ;}
+\>     { SET_COLUMNS; SET_STRING; return TOK_GT;}
+\<     { SET_COLUMNS; SET_STRING; return TOK_LT;}
+\>\=   { SET_COLUMNS; SET_STRING; return TOK_GE;}
+\<\=   { SET_COLUMNS; SET_STRING; return TOK_LE;}
+\!\=   { SET_COLUMNS; SET_STRING; return TOK_NE;}
+\+     { SET_COLUMNS; SET_STRING; return TOK_PLUS;}
+\-     { SET_COLUMNS; SET_STRING; return TOK_MINUS;}
+\*     { SET_COLUMNS; SET_STRING; return TOK_MULT;}
+\/     { SET_COLUMNS; SET_STRING; return TOK_DIV;}
+\%     { SET_COLUMNS; SET_STRING; return TOK_MOD;}
+\:     { SET_COLUMNS; SET_STRING; return TOK_COLON;}
+\(     { SET_COLUMNS; SET_STRING; return TOK_LP;}
+\)     { SET_COLUMNS; SET_STRING; return TOK_RP;}
+
+[      \r]             {}
+\"[^"]*\"   {SET_COLUMNS; SET_STRING; return TOKEN;}
+
+[\n]   {/* what to do with eol */}
+[0-9]+         {   SET_COLUMNS; 
+                               yylval_param->val = (struct val *)calloc(sizeof(struct val),1);
+                               yylval_param->val->type = AST_EXPR_integer; 
+                               yylval_param->val->u.i = atoi(yytext); 
+                               return TOKEN;}
+[a-zA-Z0-9,.?';{}\\_^%$#@!]+   {SET_COLUMNS; SET_STRING; return TOKEN;}
+
+%%
+
+/* I'm putting the interface routine to the whole parse here in the flexer input file
+   mainly because of all the flexer initialization that has to be done. Shouldn't matter
+   where it is, as long as it's somewhere. I didn't want to define a prototype for the
+   ast_yy_scan_string in the .y file, because then, I'd have to define YY_BUFFER_STATE there...
+       UGH! that would be inappropriate. */
+
+int ast_yyparse( void *); /* need to/should define this prototype for the call to yyparse */
+char *ast_expr(char *arg); /* and this prototype for the following func */
+int            ast_yyerror(const char *,YYLTYPE *, struct parse_io *); /* likewise */
+
+char *ast_expr (char *arg)
+{
+       struct parse_io *io;
+       char *pirouni;
+       
+       io = (struct parse_io *)calloc(sizeof(struct parse_io),1);
+       io->string = arg;  /* to pass to the error routine */
+       
+       ast_yylex_init(&io->scanner);
+       
+       ast_yy_scan_string(arg,io->scanner);
+       
+       ast_yyparse ((void *)io);
+
+       ast_yylex_destroy(io->scanner);
+       
+
+       if (io->val==NULL) {
+               pirouni=strdup("0");
+               return(pirouni);
+       } else {
+               if (io->val->type == AST_EXPR_integer) {
+                       pirouni=malloc(256);
+                       sprintf (pirouni,"%lld", (long long)io->val->u.i);
+               }
+               else {
+                       pirouni=strdup(io->val->u.s);
+               }
+               free(io->val);
+       }
+       free(io);
+       return(pirouni);
+}
+
+int ast_yyerror (const char *s,  yyltype *loc, struct parse_io *parseio )
+{      
+       struct yyguts_t * yyg = (struct yyguts_t*)(parseio->scanner);
+       char spacebuf[8000]; /* best safe than sorry */
+       char spacebuf2[8000]; /* best safe than sorry */
+       int i=0;
+       spacebuf[0] = 0;
+       
+#ifdef WHEN_LOC_MEANS_SOMETHING
+       if( loc->first_column > 7990 ) /* if things get out of whack, why crash? */
+               loc->first_column = 7990;
+       if( loc->last_column > 7990 )
+               loc->last_column = 7990;
+       for(i=0;i<loc->first_column;i++) spacebuf[i] = ' ';
+       for(   ;i<loc->last_column;i++) spacebuf[i] = '^';
+       spacebuf[i] = 0;
+#endif
+       for(i=0;i< (int)(yytext - YY_CURRENT_BUFFER_LVALUE->yy_ch_buf);i++) spacebuf2[i] = ' ';  /* uh... assuming yyg is defined, then I can use the yycolumn macro,
+                                                                                                       which is the same thing as... get this:
+                                                                                                       yyg->yy_buffer_stack[yyg->yy_buffer_stack_top]->yy_bs_column
+                                                                                                       I was tempted to just use yy_buf_pos in the STATE, but..., well:
+                                                                                                               a. the yy_buf_pos is the current position in the buffer, which
+                                                                                                                       may not relate to the entire string/buffer because of the
+                                                                                                                       buffering.
+                                                                                                               b. but, analysis of the situation is that when you use the
+                                                                                                                       yy_scan_string func, it creates a single buffer the size of
+                                                                                                                       string, so the two would be the same... 
+                                                                                                       so, in the end, the yycolumn macro is available, shorter, therefore easier. */
+       spacebuf2[i++]='^';
+       spacebuf2[i]= 0;
+
+#ifdef STANDALONE
+       /* easier to read in the standalone version */
+       printf("ast_yyerror(): syntax error: %s; Input:\n%s\n%s\n",  
+                       s, parseio->string,spacebuf2);
+#else
+       ast_log(LOG_WARNING,"ast_yyerror(): syntax error: %s; Input:\n%s\n%s\n",  
+                       s, parseio->string,spacebuf2);
+       ast_log(LOG_WARNING,"If you have questions, please refer to doc/README.variables2 in the asterisk source.\n");
+#endif
+       return(0);
+}
diff --git a/ast_expr2.y b/ast_expr2.y
new file mode 100755 (executable)
index 0000000..be78d46
--- /dev/null
@@ -0,0 +1,901 @@
+%{
+/* Written by Pace Willisson (pace@blitz.com) 
+ * and placed in the public domain.
+ *
+ * Largely rewritten by J.T. Conklin (jtc@wimsey.com)
+ *
+ * And then overhauled twice by Steve Murphy (murf@e-tools.com)
+ * to add double-quoted strings, allow mult. spaces, improve
+ * error messages, and then to fold in a flex scanner for the 
+ * yylex operation.
+ *
+ * $FreeBSD: src/bin/expr/expr.y,v 1.16 2000/07/22 10:59:36 se Exp $
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <locale.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <regex.h>
+#include <limits.h>
+#include <asterisk/ast_expr.h>
+#include <asterisk/logger.h>
+
+#ifdef LONG_LONG_MIN
+#define QUAD_MIN LONG_LONG_MIN
+#endif
+#ifdef LONG_LONG_MAX
+#define QUAD_MAX LONG_LONG_MAX
+#endif
+
+#  if ! defined(QUAD_MIN)
+#   define QUAD_MIN     (-0x7fffffffffffffffL-1)
+#  endif
+#  if ! defined(QUAD_MAX)
+#   define QUAD_MAX     (0x7fffffffffffffffL)
+#  endif
+
+#define YYPARSE_PARAM parseio
+#define YYLEX_PARAM ((struct parse_io *)parseio)->scanner
+#define YYERROR_VERBOSE 1
+
+/* #define ast_log fprintf
+#define LOG_WARNING stderr */
+  
+enum valtype {
+       AST_EXPR_integer, AST_EXPR_numeric_string, AST_EXPR_string
+} ;
+
+struct val {
+       enum valtype type;
+       union {
+               char *s;
+               quad_t i;
+       } u;
+} ;
+
+typedef void *yyscan_t;
+
+struct parse_io
+{
+       char *string;
+       struct val *val;
+       yyscan_t scanner;
+};
+static int             chk_div __P((quad_t, quad_t));
+static int             chk_minus __P((quad_t, quad_t, quad_t));
+static int             chk_plus __P((quad_t, quad_t, quad_t));
+static int             chk_times __P((quad_t, quad_t, quad_t));
+static void            free_value __P((struct val *));
+static int             is_zero_or_null __P((struct val *));
+static int             isstring __P((struct val *));
+static struct val      *make_integer __P((quad_t));
+static struct val      *make_str __P((const char *));
+static struct val      *op_and __P((struct val *, struct val *));
+static struct val      *op_colon __P((struct val *, struct val *));
+static struct val      *op_eqtilde __P((struct val *, struct val *));
+static struct val      *op_div __P((struct val *, struct val *));
+static struct val      *op_eq __P((struct val *, struct val *));
+static struct val      *op_ge __P((struct val *, struct val *));
+static struct val      *op_gt __P((struct val *, struct val *));
+static struct val      *op_le __P((struct val *, struct val *));
+static struct val      *op_lt __P((struct val *, struct val *));
+static struct val      *op_minus __P((struct val *, struct val *));
+static struct val      *op_negate __P((struct val *));
+static struct val      *op_compl __P((struct val *));
+static struct val      *op_ne __P((struct val *, struct val *));
+static struct val      *op_or __P((struct val *, struct val *));
+static struct val      *op_plus __P((struct val *, struct val *));
+static struct val      *op_rem __P((struct val *, struct val *));
+static struct val      *op_times __P((struct val *, struct val *));
+static quad_t          to_integer __P((struct val *));
+static void            to_string __P((struct val *));
+
+/* uh, if I want to predeclare yylex with a YYLTYPE, I have to predeclare the yyltype... sigh */
+typedef struct yyltype
+{
+  int first_line;
+  int first_column;
+
+  int last_line;
+  int last_column;
+} yyltype;
+
+# define YYLTYPE yyltype
+# define YYLTYPE_IS_TRIVIAL 1
+
+/* we will get warning about no prototype for yylex! But we can't
+   define it here, we have no definition yet for YYSTYPE. */
+
+int            ast_yyerror(const char *,YYLTYPE *, struct parse_io *);
+/* I wanted to add args to the yyerror routine, so I could print out
+   some useful info about the error. Not as easy as it looks, but it
+   is possible. */
+#define ast_yyerror(x) ast_yyerror(x,&yyloc,parseio)
+
+%}
+%pure-parser
+%locations
+/* %debug  for when you are having big problems */
+
+/* %name-prefix="ast_yy" */
+
+%union
+{
+       struct val *val;
+}
+
+/* IN_ANOTHER_LIFE
+%{
+static int             ast_yylex __P((YYSTYPE *, YYLTYPE *, yyscan_t));
+%}
+*/
+
+%left <val> TOK_OR
+%left <val> TOK_AND
+%left <val> TOK_EQ TOK_GT TOK_LT TOK_GE TOK_LE TOK_NE
+%left <val> TOK_PLUS TOK_MINUS
+%left <val> TOK_MULT TOK_DIV TOK_MOD
+%left <val> TOK_COMPL TOK_EQTILDE
+%left UMINUS
+%left <val> TOK_COLON
+%left <val> TOK_RP TOK_LP
+
+%token <val> TOKEN
+%type <val> start expr
+
+%%
+
+start: expr { ((struct parse_io *)parseio)->val = (struct val *)calloc(sizeof(struct val),1);
+              ((struct parse_io *)parseio)->val->type = $$->type;
+              ((struct parse_io *)parseio)->val->u.s = $$->u.s; }
+       ;
+
+expr:  TOKEN   { $$= $1;}
+       | TOK_LP expr TOK_RP { $$ = $2; 
+                              @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                  @$.first_line=0; @$.last_line=0;}
+       | expr TOK_OR expr { $$ = op_or ($1, $3);
+                         @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                @$.first_line=0; @$.last_line=0;}
+       | expr TOK_AND expr { $$ = op_and ($1, $3); 
+                             @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                          @$.first_line=0; @$.last_line=0;}
+       | expr TOK_EQ expr { $$ = op_eq ($1, $3); 
+                            @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                @$.first_line=0; @$.last_line=0;}
+       | expr TOK_GT expr { $$ = op_gt ($1, $3);
+                         @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                @$.first_line=0; @$.last_line=0;}
+       | expr TOK_LT expr { $$ = op_lt ($1, $3); 
+                            @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                @$.first_line=0; @$.last_line=0;}
+       | expr TOK_GE expr  { $$ = op_ge ($1, $3); 
+                             @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                 @$.first_line=0; @$.last_line=0;}
+       | expr TOK_LE expr  { $$ = op_le ($1, $3); 
+                             @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                 @$.first_line=0; @$.last_line=0;}
+       | expr TOK_NE expr  { $$ = op_ne ($1, $3); 
+                             @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                 @$.first_line=0; @$.last_line=0;}
+       | expr TOK_PLUS expr { $$ = op_plus ($1, $3); 
+                              @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                  @$.first_line=0; @$.last_line=0;}
+       | expr TOK_MINUS expr { $$ = op_minus ($1, $3); 
+                               @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                       @$.first_line=0; @$.last_line=0;}
+       | TOK_MINUS expr %prec UMINUS { $$ = op_negate ($2); 
+                               @$.first_column = @1.first_column; @$.last_column = @2.last_column; 
+                                                       @$.first_line=0; @$.last_line=0;}
+       | TOK_COMPL expr %prec UMINUS { $$ = op_compl ($2); 
+                               @$.first_column = @1.first_column; @$.last_column = @2.last_column; 
+                                                       @$.first_line=0; @$.last_line=0;}
+       | expr TOK_MULT expr { $$ = op_times ($1, $3); 
+                              @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                  @$.first_line=0; @$.last_line=0;}
+       | expr TOK_DIV expr { $$ = op_div ($1, $3); 
+                             @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                 @$.first_line=0; @$.last_line=0;}
+       | expr TOK_MOD expr { $$ = op_rem ($1, $3); 
+                             @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                 @$.first_line=0; @$.last_line=0;}
+       | expr TOK_COLON expr { $$ = op_colon ($1, $3); 
+                               @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                       @$.first_line=0; @$.last_line=0;}
+       | expr TOK_EQTILDE expr { $$ = op_eqtilde ($1, $3); 
+                               @$.first_column = @1.first_column; @$.last_column = @3.last_column; 
+                                                       @$.first_line=0; @$.last_line=0;}
+       ;
+
+%%
+
+static struct val *
+make_integer (quad_t i)
+{
+       struct val *vp;
+
+       vp = (struct val *) malloc (sizeof (*vp));
+       if (vp == NULL) {
+               ast_log(LOG_WARNING, "malloc() failed\n");
+               return(NULL);
+       }
+
+       vp->type = AST_EXPR_integer;
+       vp->u.i  = i;
+       return vp; 
+}
+
+static struct val *
+make_str (const char *s)
+{
+       struct val *vp;
+       size_t i;
+       int isint;
+
+       vp = (struct val *) malloc (sizeof (*vp));
+       if (vp == NULL || ((vp->u.s = strdup (s)) == NULL)) {
+               ast_log(LOG_WARNING,"malloc() failed\n");
+               return(NULL);
+       }
+
+       for(i = 1, isint = isdigit(s[0]) || s[0] == '-';
+           isint && i < strlen(s);
+           i++)
+       {
+               if(!isdigit(s[i]))
+                        isint = 0;
+       }
+
+       if (isint)
+               vp->type = AST_EXPR_numeric_string;
+       else    
+               vp->type = AST_EXPR_string;
+
+       return vp;
+}
+
+
+static void
+free_value (struct val *vp)
+{      
+       if (vp==NULL) {
+               return;
+       }
+       if (vp->type == AST_EXPR_string || vp->type == AST_EXPR_numeric_string)
+               free (vp->u.s); 
+}
+
+
+static quad_t
+to_integer (struct val *vp)
+{
+       quad_t i;
+
+       if (vp == NULL) {
+               ast_log(LOG_WARNING,"vp==NULL in to_integer()\n");
+               return(0);
+       }
+
+       if (vp->type == AST_EXPR_integer)
+               return 1;
+
+       if (vp->type == AST_EXPR_string)
+               return 0;
+
+       /* vp->type == AST_EXPR_numeric_string, make it numeric */
+       errno = 0;
+       i  = strtoq(vp->u.s, (char**)NULL, 10);
+       if (errno != 0) {
+               free(vp->u.s);
+               ast_log(LOG_WARNING,"overflow\n");
+               return(0);
+       }
+       free (vp->u.s);
+       vp->u.i = i;
+       vp->type = AST_EXPR_integer;
+       return 1;
+}
+
+static void
+strip_quotes(struct val *vp)
+{
+       if (vp->type != AST_EXPR_string && vp->type != AST_EXPR_numeric_string)
+               return;
+       
+       if( vp->u.s[0] == '"' && vp->u.s[strlen(vp->u.s)-1] == '"' )
+       {
+               char *f, *t;
+               f = vp->u.s;
+               t = vp->u.s;
+               
+               while( *f )
+               {
+                       if( *f  && *f != '"' )
+                               *t++ = *f++;
+                       else
+                               f++;
+               }
+               *t = *f;
+       }
+}
+
+static void
+to_string (struct val *vp)
+{
+       char *tmp;
+
+       if (vp->type == AST_EXPR_string || vp->type == AST_EXPR_numeric_string)
+               return;
+
+       tmp = malloc ((size_t)25);
+       if (tmp == NULL) {
+               ast_log(LOG_WARNING,"malloc() failed\n");
+               return;
+       }
+
+       sprintf (tmp, "%lld", (long long)vp->u.i);
+       vp->type = AST_EXPR_string;
+       vp->u.s  = tmp;
+}
+
+
+static int
+isstring (struct val *vp)
+{
+       /* only TRUE if this string is not a valid integer */
+       return (vp->type == AST_EXPR_string);
+}
+
+
+static int
+is_zero_or_null (struct val *vp)
+{
+       if (vp->type == AST_EXPR_integer) {
+               return (vp->u.i == 0);
+       } else {
+               return (*vp->u.s == 0 || (to_integer (vp) && vp->u.i == 0));
+       }
+       /* NOTREACHED */
+}
+
+#ifdef STANDALONE
+
+void ast_log(int level, const char *file, int line, const char *function, const char *fmt, ...)
+{
+       printf("LOG: lev:%d file:%s  line:%d func: %s  fmt:%s\n",
+                  level, file, line, function, fmt);
+       fflush(stdout);
+}
+
+int main(int argc,char **argv) {
+       char *s;
+
+       s=ast_expr(argv[1]);
+
+       printf("=====%s======\n",s);
+}
+
+#endif
+
+#undef ast_yyerror
+#define ast_yyerror(x) ast_yyerror(x, YYLTYPE *yylloc, struct parse_io *parseio)
+
+/* I put the ast_yyerror func in the flex input file,
+   because it refers to the buffer state. Best to
+   let it access the BUFFER stuff there and not trying
+   define all the structs, macros etc. in this file! */
+
+
+static struct val *
+op_or (struct val *a, struct val *b)
+{
+       if (is_zero_or_null (a)) {
+               free_value (a);
+               return (b);
+       } else {
+               free_value (b);
+               return (a);
+       }
+}
+               
+static struct val *
+op_and (struct val *a, struct val *b)
+{
+       if (is_zero_or_null (a) || is_zero_or_null (b)) {
+               free_value (a);
+               free_value (b);
+               return (make_integer ((quad_t)0));
+       } else {
+               free_value (b);
+               return (a);
+       }
+}
+
+static struct val *
+op_eq (struct val *a, struct val *b)
+{
+       struct val *r; 
+
+       if (isstring (a) || isstring (b)) {
+               to_string (a);
+               to_string (b);  
+               r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) == 0));
+       } else {
+               (void)to_integer(a);
+               (void)to_integer(b);
+               r = make_integer ((quad_t)(a->u.i == b->u.i));
+       }
+
+       free_value (a);
+       free_value (b);
+       return r;
+}
+
+static struct val *
+op_gt (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (isstring (a) || isstring (b)) {
+               to_string (a);
+               to_string (b);
+               r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) > 0));
+       } else {
+               (void)to_integer(a);
+               (void)to_integer(b);
+               r = make_integer ((quad_t)(a->u.i > b->u.i));
+       }
+
+       free_value (a);
+       free_value (b);
+       return r;
+}
+
+static struct val *
+op_lt (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (isstring (a) || isstring (b)) {
+               to_string (a);
+               to_string (b);
+               r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) < 0));
+       } else {
+               (void)to_integer(a);
+               (void)to_integer(b);
+               r = make_integer ((quad_t)(a->u.i < b->u.i));
+       }
+
+       free_value (a);
+       free_value (b);
+       return r;
+}
+
+static struct val *
+op_ge (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (isstring (a) || isstring (b)) {
+               to_string (a);
+               to_string (b);
+               r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) >= 0));
+       } else {
+               (void)to_integer(a);
+               (void)to_integer(b);
+               r = make_integer ((quad_t)(a->u.i >= b->u.i));
+       }
+
+       free_value (a);
+       free_value (b);
+       return r;
+}
+
+static struct val *
+op_le (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (isstring (a) || isstring (b)) {
+               to_string (a);
+               to_string (b);
+               r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) <= 0));
+       } else {
+               (void)to_integer(a);
+               (void)to_integer(b);
+               r = make_integer ((quad_t)(a->u.i <= b->u.i));
+       }
+
+       free_value (a);
+       free_value (b);
+       return r;
+}
+
+static struct val *
+op_ne (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (isstring (a) || isstring (b)) {
+               to_string (a);
+               to_string (b);
+               r = make_integer ((quad_t)(strcoll (a->u.s, b->u.s) != 0));
+       } else {
+               (void)to_integer(a);
+               (void)to_integer(b);
+               r = make_integer ((quad_t)(a->u.i != b->u.i));
+       }
+
+       free_value (a);
+       free_value (b);
+       return r;
+}
+
+static int
+chk_plus (quad_t a, quad_t b, quad_t r)
+{
+       /* sum of two positive numbers must be positive */
+       if (a > 0 && b > 0 && r <= 0)
+               return 1;
+       /* sum of two negative numbers must be negative */
+       if (a < 0 && b < 0 && r >= 0)
+               return 1;
+       /* all other cases are OK */
+       return 0;
+}
+
+static struct val *
+op_plus (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (!to_integer (a)) {
+               ast_log(LOG_WARNING,"non-numeric argument\n");
+               if (!to_integer (b)) {
+                       free_value(a);
+                       free_value(b);
+                       return make_integer(0);
+               } else {
+                       free_value(a);
+                       return (b);
+               }
+       } else if (!to_integer(b)) {
+               free_value(b);
+               return (a);
+       }
+
+       r = make_integer (/*(quad_t)*/(a->u.i + b->u.i));
+       if (chk_plus (a->u.i, b->u.i, r->u.i)) {
+               ast_log(LOG_WARNING,"overflow\n");
+       }
+       free_value (a);
+       free_value (b);
+       return r;
+}
+
+static int
+chk_minus (quad_t a, quad_t b, quad_t r)
+{
+       /* special case subtraction of QUAD_MIN */
+       if (b == QUAD_MIN) {
+               if (a >= 0)
+                       return 1;
+               else
+                       return 0;
+       }
+       /* this is allowed for b != QUAD_MIN */
+       return chk_plus (a, -b, r);
+}
+
+static struct val *
+op_minus (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (!to_integer (a)) {
+               ast_log(LOG_WARNING, "non-numeric argument\n");
+               if (!to_integer (b)) {
+                       free_value(a);
+                       free_value(b);
+                       return make_integer(0);
+               } else {
+                       r = make_integer(0 - b->u.i);
+                       free_value(a);
+                       free_value(b);
+                       return (r);
+               }
+       } else if (!to_integer(b)) {
+               ast_log(LOG_WARNING, "non-numeric argument\n");
+               free_value(b);
+               return (a);
+       }
+
+       r = make_integer (/*(quad_t)*/(a->u.i - b->u.i));
+       if (chk_minus (a->u.i, b->u.i, r->u.i)) {
+               ast_log(LOG_WARNING, "overflow\n");
+       }
+       free_value (a);
+       free_value (b);
+       return r;
+}
+
+static struct val *
+op_negate (struct val *a)
+{
+       struct val *r;
+
+       if (!to_integer (a) ) {
+               free_value(a);
+               ast_log(LOG_WARNING, "non-numeric argument\n");
+               return make_integer(0);
+       }
+
+       r = make_integer (/*(quad_t)*/(- a->u.i));
+       if (chk_minus (0, a->u.i, r->u.i)) {
+               ast_log(LOG_WARNING, "overflow\n");
+       }
+       free_value (a);
+       return r;
+}
+
+static struct val *
+op_compl (struct val *a)
+{
+       int v1 = 1;
+       struct val *r;
+       
+       if( !a )
+       {
+               v1 = 0;
+       }
+       else
+       {
+               switch( a->type )
+               {
+               case AST_EXPR_integer:
+                       if( a->u.i == 0 )
+                               v1 = 0;
+                       break;
+                       
+               case AST_EXPR_string:
+                       if( a->u.s == 0 )
+                               v1 = 0;
+                       else
+                       {
+                               if( a->u.s[0] == 0 )
+                                       v1 = 0;
+                               else if (strlen(a->u.s) == 1 && a->u.s[0] == '0' )
+                                       v1 = 0;
+                       }
+                       break;
+                       
+               case AST_EXPR_numeric_string:
+                       if( a->u.s == 0 )
+                               v1 = 0;
+                       else
+                       {
+                               if( a->u.s[0] == 0 )
+                                       v1 = 0;
+                               else if (strlen(a->u.s) == 1 && a->u.s[0] == '0' )
+                                       v1 = 0;
+                       }
+                       break;
+               }
+       }
+       
+       r = make_integer (!v1);
+       free_value (a);
+       return r;
+}
+
+static int
+chk_times (quad_t a, quad_t b, quad_t r)
+{
+       /* special case: first operand is 0, no overflow possible */
+       if (a == 0)
+               return 0;
+       /* cerify that result of division matches second operand */
+       if (r / a != b)
+               return 1;
+       return 0;
+}
+
+static struct val *
+op_times (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (!to_integer (a) || !to_integer (b)) {
+               free_value(a);
+               free_value(b);
+               ast_log(LOG_WARNING, "non-numeric argument\n");
+               return(make_integer(0));
+       }
+
+       r = make_integer (/*(quad_t)*/(a->u.i * b->u.i));
+       if (chk_times (a->u.i, b->u.i, r->u.i)) {
+               ast_log(LOG_WARNING, "overflow\n");
+       }
+       free_value (a);
+       free_value (b);
+       return (r);
+}
+
+static int
+chk_div (quad_t a, quad_t b)
+{
+       /* div by zero has been taken care of before */
+       /* only QUAD_MIN / -1 causes overflow */
+       if (a == QUAD_MIN && b == -1)
+               return 1;
+       /* everything else is OK */
+       return 0;
+}
+
+static struct val *
+op_div (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (!to_integer (a)) {
+               free_value(a);
+               free_value(b);
+               ast_log(LOG_WARNING, "non-numeric argument\n");
+               return make_integer(0);
+       } else if (!to_integer (b)) {
+               free_value(a);
+               free_value(b);
+               ast_log(LOG_WARNING, "non-numeric argument\n");
+               return make_integer(INT_MAX);
+       }
+
+       if (b->u.i == 0) {
+               ast_log(LOG_WARNING, "division by zero\n");             
+               free_value(a);
+               free_value(b);
+               return make_integer(INT_MAX);
+       }
+
+       r = make_integer (/*(quad_t)*/(a->u.i / b->u.i));
+       if (chk_div (a->u.i, b->u.i)) {
+               ast_log(LOG_WARNING, "overflow\n");
+       }
+       free_value (a);
+       free_value (b);
+       return r;
+}
+       
+static struct val *
+op_rem (struct val *a, struct val *b)
+{
+       struct val *r;
+
+       if (!to_integer (a) || !to_integer (b)) {
+               ast_log(LOG_WARNING, "non-numeric argument\n");
+               free_value(a);
+               free_value(b);
+               return make_integer(0);
+       }
+
+       if (b->u.i == 0) {
+               ast_log(LOG_WARNING, "div by zero\n");
+               free_value(a);
+               return(b);
+       }
+
+       r = make_integer (/*(quad_t)*/(a->u.i % b->u.i));
+       /* chk_rem necessary ??? */
+       free_value (a);
+       free_value (b);
+       return r;
+}
+       
+
+static struct val *
+op_colon (struct val *a, struct val *b)
+{
+       regex_t rp;
+       regmatch_t rm[2];
+       char errbuf[256];
+       int eval;
+       struct val *v;
+
+       /* coerce to both arguments to strings */
+       to_string(a);
+       to_string(b);
+       /* strip double quotes from both -- they'll screw up the pattern, and the search string starting at ^ */
+       strip_quotes(a);
+       strip_quotes(b);
+       /* compile regular expression */
+       if ((eval = regcomp (&rp, b->u.s, REG_EXTENDED)) != 0) {
+               regerror (eval, &rp, errbuf, sizeof(errbuf));
+               ast_log(LOG_WARNING,"regcomp() error : %s",errbuf);
+               free_value(a);
+               free_value(b);
+               return make_str("");            
+       }
+
+       /* compare string against pattern */
+       /* remember that patterns are anchored to the beginning of the line */
+       if (regexec(&rp, a->u.s, (size_t)2, rm, 0) == 0 && rm[0].rm_so == 0) {
+               if (rm[1].rm_so >= 0) {
+                       *(a->u.s + rm[1].rm_eo) = '\0';
+                       v = make_str (a->u.s + rm[1].rm_so);
+
+               } else {
+                       v = make_integer ((quad_t)(rm[0].rm_eo - rm[0].rm_so));
+               }
+       } else {
+               if (rp.re_nsub == 0) {
+                       v = make_integer ((quad_t)0);
+               } else {
+                       v = make_str ("");
+               }
+       }
+
+       /* free arguments and pattern buffer */
+       free_value (a);
+       free_value (b);
+       regfree (&rp);
+
+       return v;
+}
+       
+
+static struct val *
+op_eqtilde (struct val *a, struct val *b)
+{
+       regex_t rp;
+       regmatch_t rm[2];
+       char errbuf[256];
+       int eval;
+       struct val *v;
+
+       /* coerce to both arguments to strings */
+       to_string(a);
+       to_string(b);
+       /* strip double quotes from both -- they'll screw up the pattern, and the search string starting at ^ */
+       strip_quotes(a);
+       strip_quotes(b);
+       /* compile regular expression */
+       if ((eval = regcomp (&rp, b->u.s, REG_EXTENDED)) != 0) {
+               regerror (eval, &rp, errbuf, sizeof(errbuf));
+               ast_log(LOG_WARNING,"regcomp() error : %s",errbuf);
+               free_value(a);
+               free_value(b);
+               return make_str("");            
+       }
+
+       /* compare string against pattern */
+       /* remember that patterns are anchored to the beginning of the line */
+       if (regexec(&rp, a->u.s, (size_t)2, rm, 0) == 0 ) {
+               if (rm[1].rm_so >= 0) {
+                       *(a->u.s + rm[1].rm_eo) = '\0';
+                       v = make_str (a->u.s + rm[1].rm_so);
+
+               } else {
+                       v = make_integer ((quad_t)(rm[0].rm_eo - rm[0].rm_so));
+               }
+       } else {
+               if (rp.re_nsub == 0) {
+                       v = make_integer ((quad_t)0);
+               } else {
+                       v = make_str ("");
+               }
+       }
+
+       /* free arguments and pattern buffer */
+       free_value (a);
+       free_value (b);
+       regfree (&rp);
+
+       return v;
+}
index 05955bd..ff5313f 100755 (executable)
@@ -1,5 +1,6 @@
+----------------------------
 Asterisk dial plan variables 
----------------------------
+----------------------------
 
 There are two levels of parameter evaluation done in the Asterisk
 dial plan in extensions.conf.
@@ -12,6 +13,15 @@ Asterisk has user-defined variables and standard variables set
 by various modules in Asterisk. These standard variables are
 listed at the end of this document.
 
+NOTE: During the Asterisk build process, the versions of bison and
+flex available on your system are probed. If you have versions of
+flex greater than or equal to 2.5.31, it will use flex to build a 
+"pure" (re-entrant) tokenizer for expressions. If you use bison version 
+greater than 1.85, it will use a bison grammar to generate a pure (re-entrant) 
+parser for $[] expressions. 
+Notes specific to the flex parser are marked with "**" at the beginning
+of the line.
+
 ___________________________
 PARAMETER QUOTING: 
 ---------------------------
@@ -123,6 +133,10 @@ considered as an expression and it is evaluated. Evaluation works similar to
 evaluation. 
 Note: The arguments and operands of the expression MUST BE separated 
 by at least one space. 
+** Using the Flex generated tokenizer, this is no longer the case. Spaces
+** are only required where they would seperate tokens that would normally
+** be merged into a single token. Using the new tokenizer, spaces can be
+** used freely.
 
 
 For example, after the sequence: 
@@ -132,6 +146,11 @@ exten => 1,2,Set(koko=$[2 * ${lala}])
 
 the value of variable koko is "6".
 
+** Using the new Flex generated tokenizer, the expressions above are still
+** legal, but so are the following:
+** exten => 1,1,Set(lala=$[1+2])
+** exten => 1,2,Set(koko=$[2*    ${lala}])
+
 And, further:
 
 exten => 1,1,Set(lala=$[1+2]);
@@ -141,15 +160,19 @@ token "1+2" are not numbers, it will be evaluated as the string "1+2". Again,
 please do not forget, that this is a very simple parsing engine, and it
 uses a space (at least one), to separate "tokens".
 
+** Please note that spaces are not required to separate tokens if you have
+** Flex version 2.5.31 or higher on your system.
 and, further:
 
 exten => 1,1,Set,"lala=$[  1 +    2   ]";
 
 will parse as intended. Extra spaces are ignored.
 
-___________________________
-SPACES INSIDE VARIABLE
----------------------------
+
+______________________________
+SPACES INSIDE VARIABLE VALUES
+------------------------------
 If the variable being evaluated contains spaces, there can be problems.
 
 For these cases, double quotes around text that may contain spaces
@@ -173,7 +196,7 @@ DELOREAN MOTORS : Privacy Manager
 
 and will result in syntax errors, because token DELOREAN is immediately
 followed by token MOTORS and the expression parser will not know how to 
-evaluate this expression.
+evaluate this expression, because it does not match its grammar.
 
 _____________________
 OPERATORS
@@ -204,6 +227,14 @@ with equal precedence are grouped within { } symbols.
              Return the results of multiplication, integer division, or
              remainder of integer-valued arguments.
 
+**   - expr1
+**          Return the result of subtracting expr1 from 0.
+**
+**   ! expr1
+**          Return the result of a logical complement of expr1.
+**          In other words, if expr1 is null, 0, an empty string,
+**          or the string "0", return a 1. Otherwise, return a "0". (only with flex >= 2.5.31)
+
      expr1 : expr2
              The `:' operator matches expr1 against expr2, which must be a
              regular expression.  The regular expression is anchored to the
@@ -216,11 +247,70 @@ with equal precedence are grouped within { } symbols.
              the pattern contains a regular expression subexpression the null
              string is returned; otherwise 0.
 
+             Normally, the double quotes wrapping a string are left as part
+             of the string. This is disastrous to the : operator. Therefore,
+             before the regex match is made, beginning and ending double quote
+             characters are stripped from both the pattern and the string.
+
+**    expr1 =~ expr2
+**             Exactly the same as the ':' operator, except that the match is
+**             not anchored to the beginning of the string. Pardon any similarity
+**             to seemingly similar operators in other programming languages!
+**             (only if flex >= 2.5.31)
+
+
+
 Parentheses are used for grouping in the usual manner.
 
-The parser must be parsed with bison (bison is REQUIRED - yacc cannot 
-produce pure parsers, which are reentrant) 
+Operator precedence is applied as one would expect in any of the C
+or C derived languages.
+
+The parser must be generated with bison (bison is REQUIRED - yacc cannot 
+produce pure parsers, which are reentrant)  The same with flex, if flex
+is at 2.5.31 or greater; Re-entrant scanners were not available before that
+version.
+
+
+
+Examples
 
+** "One Thousand Five Hundred" =~ "(T[^ ]+)"
+**     returns: Thousand
+
+** "One Thousand Five Hundred" =~ "T[^ ]+"
+**     returns: 8
+
+ "One Thousand Five Hundred" : "T[^ ]+"
+       returns: 0
+
+ "8015551212" : "(...)"
+       returns: 801
+
+ "3075551212":"...(...)"
+       returns: 555
+
+** ! "One Thousand Five Hundred" =~ "T[^ ]+"
+**     returns: 0 (because it applies to the string, which is non-null, which it turns to "0",
+                    and then looks for the pattern in the "0", and doesn't find it)
+
+** !( "One Thousand Five Hundred" : "T[^ ]+" )
+**     returns: 1  (because the string doesn't start with a word starting with T, so the
+                     match evals to 0, and the ! operator inverts it to 1 ).
+
+ 2 + 8 / 2
+       returns 6. (because of operator precedence; the division is done first, then the addition).
+
+** 2+8/2
+**     returns 6. Spaces aren't necessary.
+
+**(2+8)/2
+**     returns 5, of course.
+
+Of course, all of the above examples use constants, but would work the same if any of the
+numeric or string constants were replaced with a variable reference ${CALLERIDNUM}, for
+instance.
+
 ___________________________
 CONDITIONALS
 ---------------------------
@@ -277,6 +367,26 @@ going to be somewhere between the last '^' on the second line, and the
 '^' on the third line. That's right, in the example above, there are two
 '&' chars, separated by a space, and this is a definite no-no!
 
+** WITH FLEX >= 2.5.31, this has changed slightly. The line showing the 
+** part of the expression that was successfully parsed has been dropped,
+** and the parse error is explained in a somewhat cryptic format in the log.
+** 
+** The same line in extensions.conf as above, will now generate an error 
+** message in /var/log/asterisk/messages that looks like this:
+**
+** Jul 15 21:27:49 WARNING[1251240752]: ast_yyerror(): syntax error: parse error, unexpected TOK_AND, expecting TOK_MINUS or TOK_LP or TOKEN; Input:
+** "3072312154"  = "3071234567" & & "Steves Extension" : "Privacy Manager" 
+**                                ^
+**
+** The log line tells you that a syntax error was encountered. It now
+** also tells you (in grand standard bison format) that it hit an "AND" (&)
+** token unexpectedly, and that was hoping for for a MINUS (-), LP (left parenthesis),
+** or a plain token (a string or number).
+** 
+** As before, the next line shows the evaluated expression, and the line after
+** that, the position of the parser in the expression when it became confused,
+** marked with the "^" character.
+
 
 ___________________________
 NULL STRINGS
@@ -306,6 +416,89 @@ whatever language you desire, be it Perl, C, C++, Cobol, RPG, Java,
 Snobol, PL/I, Scheme, Common Lisp, Shell scripts, Tcl, Forth, Modula,
 Pascal, APL, assembler, etc.
 
+----------------------------
+INCOMPATIBILITIES
+----------------------------
+
+The asterisk expression parser has undergone some evolution. It is hoped
+that the changes will be viewed as positive. 
+
+The "original" expression parser had a simple, hand-written scanner, and 
+a simple bison grammar. This was upgraded to a more involved bison grammar,
+and a hand-written scanner upgraded to allow extra spaces, and to generate
+better error diagnostics. This upgrade required bison 1.85, and a [art of the user
+community felt the pain of having to upgrade their bison version.
+
+The next upgrade included new bison and flex input files, and the makefile
+was upgraded to detect current version of both flex and bison, conditionally
+compiling and linking the new files if the versions of flex and bison would
+allow it.
+
+If you have not touched your extensions.conf files in a year or so, the
+above upgrades may cause you some heartburn in certain circumstances, as
+several changes have been made, and these will affect asterisk's behavior on 
+legacy extension.conf constructs.  The changes have been engineered
+to minimize these conflicts, but there are bound to be problems.
+
+The following list gives some (and most likely, not all) of areas
+of possible concern with "legacy" extension.conf files:
+
+1. Tokens separated by space(s).
+   Previously, tokens were separated by spaces. Thus, ' 1 + 1 ' would evaluate
+  to the value '2', but '1+1' would evaluate to the string '1+1'. If this
+  behavior was depended on, then the expression evaluation will break. '1+1'
+  will now evaluate to '2', and something is not going to work right.
+  To keep such strings from being evaluated, simply wrap them in double 
+  quotes: '  "1+1" '
+
+2. The colon operator. In versions previous to double quoting, the
+   colon operator takes the right hand string, and using it as a 
+   regex pattern, looks for it in the left hand string. It is given
+   an implicit ^ operator at the beginning, meaning the pattern 
+   will match only at the beginning of the left hand string. 
+     If the pattern or the matching string had double quotes around
+   them, these could get in the way of the pattern match. Now,
+   the wrapping double quotes are stripped from both the pattern 
+   and the left hand string before applying the pattern. This
+   was done because it recognized that the new way of
+   scanning the expression doesn't use spaces to separate tokens,
+   and the average regex expression is full of operators that 
+   the scanner will recognize as expression operators. Thus, unless
+   the pattern is wrapped in double quotes, there will be trouble.
+   For instance,      ${VAR1} : (Who|What*)+
+   may have have worked before, but unless you wrap the pattern
+   in double quotes now, look out for trouble! This is better:
+         "${VAR1}" : "(Who|What*)+"
+   and should work as previous.
+
+3. Variables and Double Quotes
+   Before these changes, if a variable's value contained one or more double
+   quotes, it was no reason for concern. It is now!
+
+4. LE, GE, NE operators removed. The code supported these operators,
+   but they were not documented. The symbolic operators, <=, >=, and !=
+   should be used instead.
+
+**5. flex 2.5.31 or greater should be used. Bison-1.875 or greater. In
+**   the case of flex, earlier versions do not generate 'pure', or 
+**   reentrant C scanners. In the case of bison-1.875, earlier versions
+**   didn't support the location tracking mechanism.
+
+**    http://ftp.gnu.org/gnu/bison/bison-1.875.tar.bz2
+**    http://prdownloads.sourceforge.net/lex/flex-2.5.31.tar.bz2?download
+**     or http://lex.sourceforge.net/
+
+**6.  Added the unary '-' operator. So you can 3+ -4 and get -1.
+
+**7.  Added the unary '!' operator, which is a logical complement.
+**    Basically, if the string or number is null, empty, or '0',
+**    a '1' is returned. Otherwise a '0' is returned.
+
+**8.  Added the '=~' operator, just in case someone is just looking for
+**    match anywhere in the string. The only diff with the ':' is that
+**    match doesn't have to be anchored to the beginning of the string.
+
+
 ---------------------------------------------------------
 Asterisk standard channel variables 
 ---------------------------------------------------------
diff --git a/vercomp.sh b/vercomp.sh
new file mode 100755 (executable)
index 0000000..8ab64c7
--- /dev/null
@@ -0,0 +1,163 @@
+#! /bin/bash
+
+### flex just outputs a single line:
+
+## flex version 2.5.4
+
+
+### but bison is a bit more wordy
+
+## bison (GNU Bison) 1.875c
+## Written by Robert Corbett and Richard Stallman.
+## 
+## Copyright (C) 2003 Free Software Foundation, Inc.
+## This is free software; see the source for copying conditions.  There is NO
+## warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+### based on this, the version number of the program:
+###   a. in the first line of output
+###   b. is the last "word" of that line
+
+program=$1
+comparefunc=$2
+argver=$3
+
+progver1=`$program --version | head -1`
+
+[[ $progver1 =~ '([^ ]+$)'  ]]
+
+progver=$BASH_REMATCH
+
+progver2=$progver
+numprogverlist=0
+
+while [[ $progver2 =~ '^([^.]+)\.(.*)' ]]; do
+       progver2=${BASH_REMATCH[2]}
+       progverlist[$numprogverlist]=${BASH_REMATCH[1]}
+       progverlist[$(( ${numprogverlist}+1 ))]=${BASH_REMATCH[2]}
+
+##     echo ${BASH_REMATCH[0]}
+##     echo ${BASH_REMATCH[1]}
+##     echo ${BASH_REMATCH[2]}
+       (( numprogverlist=$(( $numprogverlist+1 )) ))
+
+done
+       (( numprogverlist=$(( $numprogverlist+1 )) ))
+       
+##     echo number of elements = $numprogverlist
+##     echo element 0 = ${progverlist[0]}
+##     echo element 1 = ${progverlist[1]}
+##     echo element 2 = ${progverlist[2]}
+
+argver2=$argver
+numargverlist=0
+
+while [[ $argver2 =~ '^([^.]+)\.(.*)' ]]; do
+       argver2=${BASH_REMATCH[2]}
+       argverlist[$numargverlist]=${BASH_REMATCH[1]}
+       argverlist[$(( ${numargverlist}+1 ))]=${BASH_REMATCH[2]}
+
+##     echo ${BASH_REMATCH[0]}
+##     echo ${BASH_REMATCH[1]}
+##     echo ${BASH_REMATCH[2]}
+       (( numargverlist=$(( $numargverlist+1 )) ))
+
+done
+       (( numargverlist=$(( $numargverlist+1 )) ))
+       
+##     echo number of argver elements = $numargverlist
+##     echo element 0 = ${argverlist[0]}
+##     echo element 1 = ${argverlist[1]}
+##     echo element 2 = ${argverlist[2]}
+
+if (( $numprogverlist < $numargverlist )); then
+       for (( i=$numprogverlist ; $i < $numargverlist ; i=$i + 1 )) ; do
+##             echo setting progverlist "[" $i "]" to 0
+               (( progverlist[$i]='0' ))
+               (( numprogverlist=${numprogverlist}+1 ))
+       done
+elif (( $numargverlist < $numprogverlist )); then
+       for (( i=$numargverlist ; $i < $numprogverlist ; i=$i + 1 )) ; do
+##             echo setting argverlist "[" $i "]" to 0
+               (( argverlist[$i]='0' ))
+               (( numargverlist=${numargverlist}+1 ))
+       done
+fi
+
+## echo numarg=$numargverlist   numprog=$numprogverlist
+## echo arg0: ${argverlist[0]}
+## echo arg1: ${argverlist[1]}
+## echo arg2: ${argverlist[2]}
+## echo prog0: ${progverlist[0]}
+## echo prog1: ${progverlist[1]}
+## echo prog2: ${progverlist[2]}
+
+## the main comparison loop 
+
+for (( i=0 ; $i < $numargverlist ; i=$i + 1 )) ; do
+##     echo i= $i
+
+       if [[ ${progverlist[$i]} =~ '^[0-9]+$' &&  ${argverlist[$i]} =~ '^[0-9]+$' ]] ; then  ## nothing but numbers
+               if (( ${progverlist[$i]} != ${argverlist[$i]} )); then
+                       if [[ ${progverlist[$i]}  -lt ${argverlist[$i]} ]]; then
+                               if [[ $comparefunc == "=" ]]; then
+                                       echo "false"
+                                       exit 0;
+                               elif [[ $comparefunc == "<" || $comparefunc == "<=" ]]; then
+                                       echo "true"
+                                       exit 0;
+                               elif [[ $comparefunc == ">" || $comparefunc == ">=" ]]; then
+                                       echo "false"
+                                       exit 0;
+                               fi
+                       elif [[ ${progverlist[$i]} -gt ${argverlist[$i]} ]]; then
+                               if [[ $comparefunc == "=" ]]; then
+                                       echo "false"
+                                       exit 0;
+                               elif [[ $comparefunc == "<" || $comparefunc == "<=" ]]; then
+                                       echo "false"
+                                       exit 0;
+                               elif [[ $comparefunc == ">" || $comparefunc == ">=" ]]; then
+                                       echo "true"
+                                       exit 0;
+                               fi
+                       fi
+               fi
+       else  ## something besides just numbers
+               if [[ ${progverlist[$i]} != ${argverlist[$i]} ]]; then
+                       if [[ ${progverlist[$i]} < ${argverlist[$i]} ]]; then
+                               if [[ $comparefunc == "=" ]]; then
+                                       echo "false"
+                                       exit 0;
+                               elif [[ $comparefunc == "<" || $comparefunc == "<=" ]]; then
+                                       echo "true"
+                                       exit 0;
+                               elif [[ $comparefunc == ">" || $comparefunc == ">=" ]]; then
+                                       echo "false"
+                                       exit 0;
+                               fi
+                       elif [[ ${progverlist[$i]} > ${argverlist[$i]} ]]; then
+                               if [[ $comparefunc == "=" ]]; then
+                                       echo "false"
+                                       exit 0;
+                               elif [[ $comparefunc == "<" || $comparefunc == "<=" ]]; then
+                                       echo "false"
+                                       exit 0;
+                               elif [[ $comparefunc == ">" || $comparefunc == ">=" ]]; then
+                                       echo "true"
+                                       exit 0;
+                               fi
+                       fi
+               fi
+       fi
+done
+
+if [[ $comparefunc == "=" ]]; then
+       echo "true"
+elif [[ $comparefunc == "<=" || $comparefunc == ">=" ]]; then
+       echo "true"
+else
+       echo "false"
+fi
+
+exit 0;