aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkpfleming <kpfleming@f38db490-d61c-443f-a65b-d21fe96a405b>2005-05-16 00:35:38 +0000
committerkpfleming <kpfleming@f38db490-d61c-443f-a65b-d21fe96a405b>2005-05-16 00:35:38 +0000
commit5e9ff3009ec0f9ca310ac94fb7a8ebf7cd2db571 (patch)
treed149948a4b2a5d510a60e6c3ddc8d04b6f57f8df
parentcd6784bf2bab3dc3c32f9a3ca92b179dc62d8cef (diff)
add upgraded expression parser (bug #2058)
git-svn-id: http://svn.digium.com/svn/asterisk/trunk@5691 f38db490-d61c-443f-a65b-d21fe96a405b
-rwxr-xr-xMakefile47
-rwxr-xr-xast_expr2.fl167
-rwxr-xr-xast_expr2.y901
-rwxr-xr-xdoc/README.variables207
-rwxr-xr-xvercomp.sh163
5 files changed, 1476 insertions, 9 deletions
diff --git a/Makefile b/Makefile
index 80f9fdc58..6cd52af18 100755
--- 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
index 000000000..fc56994ee
--- /dev/null
+++ b/ast_expr2.fl
@@ -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
index 000000000..be78d4645
--- /dev/null
+++ b/ast_expr2.y
@@ -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;
+}
diff --git a/doc/README.variables b/doc/README.variables
index 05955bdba..ff5313f31 100755
--- a/doc/README.variables
+++ b/doc/README.variables
@@ -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
index 000000000..8ab64c714
--- /dev/null
+++ b/vercomp.sh
@@ -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;