From 2a7d309debe319606d73748a8583585683728a5f Mon Sep 17 00:00:00 2001 From: "Kevin P. Fleming" <kpfleming@digium.com> Date: Mon, 16 May 2005 00:35:38 +0000 Subject: [PATCH] add upgraded expression parser (bug #2058) git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@5691 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- Makefile | 47 ++- ast_expr2.fl | 167 ++++++++ ast_expr2.y | 901 +++++++++++++++++++++++++++++++++++++++++++ doc/README.variables | 207 +++++++++- vercomp.sh | 163 ++++++++ 5 files changed, 1476 insertions(+), 9 deletions(-) create mode 100755 ast_expr2.fl create mode 100755 ast_expr2.y create mode 100755 vercomp.sh diff --git a/Makefile b/Makefile index 80f9fdc587..6cd52af186 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 0000000000..fc56994ee2 --- /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 0000000000..be78d4645f --- /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 05955bdbac..ff5313f31d 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 0000000000..8ab64c7149 --- /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; -- GitLab