From 42c49d621d9341a530bca9422d7000087593e6bb Mon Sep 17 00:00:00 2001 From: Chet Ramey Date: Fri, 17 Jan 2025 11:41:49 -0500 Subject: [PATCH] fix issue with internally quoting multibyte characters; the let builtin now suppresses additional array subscript expansion; small change to how readline export-completions displays filenames; new loadable builtin to do floating-point arithmetic --- CHANGES | 2 +- CWRU/CWRU.chlog | 38 + MANIFEST | 1 + builtins/printf.def | 134 ++- doc/bash.1 | 7 +- doc/bashref.texi | 2 + examples/loadables/Makefile.in | 6 +- examples/loadables/fltexpr.c | 1500 +++++++++++++++++++++++++++++++ expr.c | 102 ++- lib/readline/complete.c | 5 +- lib/readline/doc/history.3 | 4 +- lib/readline/doc/history.texi | 4 +- lib/readline/doc/hstech.texi | 2 +- lib/readline/doc/hsuser.texi | 2 +- lib/readline/doc/readline.3 | 4 +- lib/readline/doc/rlman.texi | 2 +- lib/readline/doc/rltech.texi | 2 +- lib/readline/doc/rluser.texi | 2 +- lib/readline/doc/rluserman.texi | 2 +- lib/readline/doc/version.texi | 2 +- shell.c | 2 +- subst.c | 6 +- 22 files changed, 1719 insertions(+), 112 deletions(-) create mode 100644 examples/loadables/fltexpr.c diff --git a/CHANGES b/CHANGES index 16cd0e35..6b742da2 100644 --- a/CHANGES +++ b/CHANGES @@ -1885,7 +1885,7 @@ n. The `test -N' operator uses nanosecond timestamp granularity if it's available. o. Bash posix mode now treats assignment statements preceding shell function - definitions the same as in its default mode, since POSIX has changed and + calls the same as in its default mode, since POSIX has changed and no longer requires those assignments to persist after the function returns (POSIX interp 654). diff --git a/CWRU/CWRU.chlog b/CWRU/CWRU.chlog index d5c1792e..dfcc9997 100644 --- a/CWRU/CWRU.chlog +++ b/CWRU/CWRU.chlog @@ -10796,3 +10796,41 @@ tests/printf7.sub,tests/cond-regexp2.sub tests/run-all, tests/run-minimal - BASHOPTS: unset or unexport as appropriate, same as SHELLOPTS From a report by Martin D Kealey + + 1/9 + --- +subst.c + - string_extract_verbatim: take into account the fact that CTLESC can + quote multibyte characters; use ADVANCE_CHAR instead of increment + Fixes bug reported by jktseug@gmail.com + + 1/10 + ---- +expr.c + - expr_bind_variable,expr_streval: suppress additional expansion if + called by the `let' builtin, whether or not array_expand_once is set + - expr_skipsubscript: suppress expansion while parsing subscripts + whether array_expand_once is set or not + Still have to change tests if this goes final + These are all conditional on shell_compatibility_level > 51 + From a bug-bash post by Greg Wooledge + + 1/14 + ---- +lib/readline/complete.c + - _rl_export_completions: use print_filename instead of fprintf to + display the possible completions so slashes and file types can + be included + From a patch from Matthew Tromp + + 1/15 + ---- +examples/loadables/fltexpr.c + - fltexpr: new loadable builtin to do floating-point arithmetic + expression evaluation and optionally print the result + + 1/16 + ---- +builtins/printf.def + - getarg(), advancearg(): cosmetic changes to make it easier to + implement %N$ format specifiers in the future diff --git a/MANIFEST b/MANIFEST index 48c7a6c4..72d684e3 100644 --- a/MANIFEST +++ b/MANIFEST @@ -784,6 +784,7 @@ examples/loadables/getconf.h f examples/loadables/getconf.c f examples/loadables/fdflags.c f examples/loadables/finfo.c f +examples/loadables/fltexpr.c f examples/loadables/cat.c f examples/loadables/csv.c f examples/loadables/dsv.c f diff --git a/builtins/printf.def b/builtins/printf.def index 0ba0e634..5321b57c 100644 --- a/builtins/printf.def +++ b/builtins/printf.def @@ -242,6 +242,12 @@ static char *vbuf, *vname; static size_t vbsize; static size_t vblen; +/* printf format numbered argument support */ +static char **narg_argv; +static int narg_argc; +static int narg_maxind; +static int narg_curind; + static intmax_t tw; static char *conv_buf; @@ -1329,16 +1335,32 @@ mklong (char *str, char *modifiers, size_t mlen) return (conv_buf); } +static inline char * +getarg (void) +{ + return (garglist ? garglist->word->word : 0); +} + +static inline void +advancearg (void) +{ + garglist = garglist->next; +} + static int getchr (void) { int ret; + char *arg; + + arg = getarg (); - if (garglist == 0) + if (arg == 0) return ('\0'); - ret = (int)garglist->word->word[0]; - garglist = garglist->next; + ret = (int)arg[0]; + + advancearg (); return ret; } @@ -1347,11 +1369,11 @@ getstr (void) { char *ret; - if (garglist == 0) + ret = getarg (); + if (ret == 0) return (""); - ret = garglist->word->word; - garglist = garglist->next; + advancearg (); return ret; } @@ -1379,23 +1401,25 @@ static int getint (int overflow_retval) { intmax_t ret; - char *ep; + char *ep, *arg; int overflow; - if (garglist == 0) + arg = getarg (); + + if (arg == 0) return (0); - if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + if (arg[0] == '\'' || arg[0] == '"') return asciicode (); errno = 0; - ret = strtoimax (garglist->word->word, &ep, 0); + ret = strtoimax (arg, &ep, 0); if (overflow = (errno == ERANGE) || (ret < INT_MIN || ret > INT_MAX)) errno = ERANGE; /* force errno */ - chk_converror (garglist->word->word, ep); + chk_converror (arg, ep); - garglist = garglist->next; + advancearg (); return (overflow ? overflow_retval : (int)ret); } @@ -1403,20 +1427,22 @@ static intmax_t getintmax (void) { intmax_t ret; - char *ep; + char *ep, *arg; - if (garglist == 0) + arg = getarg (); + + if (arg == 0) return (0); - if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + if (arg[0] == '\'' || arg[0] == '"') return asciicode (); errno = 0; - ret = strtoimax (garglist->word->word, &ep, 0); + ret = strtoimax (arg, &ep, 0); - chk_converror (garglist->word->word, ep); + chk_converror (arg, ep); - garglist = garglist->next; + advancearg (); return (ret); } @@ -1424,20 +1450,22 @@ static uintmax_t getuintmax (void) { uintmax_t ret; - char *ep; + char *ep, *arg; - if (garglist == 0) + arg = getarg (); + + if (arg == 0) return (0); - if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + if (arg[0] == '\'' || arg[0] == '"') return asciicode (); errno = 0; - ret = strtoumax (garglist->word->word, &ep, 0); + ret = strtoumax (arg, &ep, 0); - chk_converror (garglist->word->word, ep); + chk_converror (arg, ep); - garglist = garglist->next; + advancearg (); return (ret); } @@ -1445,20 +1473,22 @@ static double getdouble (void) { double ret; - char *ep; + char *ep, *arg; + + arg = getarg (); - if (garglist == 0) + if (arg == 0) return (0); - if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + if (arg[0] == '\'' || arg[0] == '"') return asciicode (); errno = 0; - ret = strtod (garglist->word->word, &ep); + ret = strtod (arg, &ep); - chk_converror (garglist->word->word, ep); + chk_converror (arg, ep); - garglist = garglist->next; + advancearg (); return (ret); } @@ -1466,20 +1496,22 @@ static floatmax_t getfloatmax (void) { floatmax_t ret; - char *ep; + char *ep, *arg; + + arg = getarg (); - if (garglist == 0) + if (arg == 0) return (0); - if (garglist->word->word[0] == '\'' || garglist->word->word[0] == '"') + if (arg[0] == '\'' || arg[0] == '"') return asciicode (); errno = 0; - ret = strtofltmax (garglist->word->word, &ep); + ret = strtofltmax (arg, &ep); - chk_converror (garglist->word->word, ep); + chk_converror (arg, ep); - garglist = garglist->next; + advancearg (); return (ret); } @@ -1488,23 +1520,25 @@ static intmax_t asciicode (void) { register intmax_t ch; + char *arg; #if defined (HANDLE_MULTIBYTE) wchar_t wc; size_t slen, mblength; #endif DECLARE_MBSTATE; + arg = getarg (); #if defined (HANDLE_MULTIBYTE) - slen = strlen (garglist->word->word+1); + slen = strlen (arg+1); wc = 0; - mblength = mbrtowc (&wc, garglist->word->word+1, slen, &state); + mblength = mbrtowc (&wc, arg+1, slen, &state); if (MB_INVALIDCH (mblength) == 0) ch = wc; /* XXX */ else #endif - ch = (unsigned char)garglist->word->word[1]; + ch = (unsigned char)arg[1]; - garglist = garglist->next; + advancearg (); return (ch); } @@ -1515,16 +1549,18 @@ getwidestr (size_t *lenp) wchar_t *ws; const char *mbs; size_t slen, mblength; + char *arg; DECLARE_MBSTATE; - if (garglist == 0) + arg = getarg (); + if (arg == 0) { if (lenp) *lenp = 0; return NULL; } - mbs = garglist->word->word; + mbs = arg; slen = strlen (mbs); ws = (wchar_t *)xmalloc ((slen + 1) * sizeof (wchar_t)); mblength = mbsrtowcs (ws, &mbs, slen + 1, &state); @@ -1535,13 +1571,13 @@ getwidestr (size_t *lenp) { int i; for (i = 0; i < slen; i++) - ws[i] = (wchar_t)garglist->word->word[i]; + ws[i] = (wchar_t)arg[i]; ws[slen] = L'\0'; if (lenp) *lenp = slen; } - garglist = garglist->next; + advancearg (); return (ws); } @@ -1550,17 +1586,19 @@ getwidechar (void) { wchar_t wc; size_t slen, mblength; + char *arg; DECLARE_MBSTATE; - if (garglist == 0) + arg = getarg (); + if (arg == 0) return L'\0'; wc = 0; - mblength = mbrtowc (&wc, garglist->word->word, locale_mb_cur_max, &state); + mblength = mbrtowc (&wc, arg, locale_mb_cur_max, &state); if (MB_INVALIDCH (mblength)) - wc = (wchar_t)garglist->word->word[0]; + wc = (wchar_t)arg[0]; - garglist = garglist->next; + advancearg (); return (wc); } diff --git a/doc/bash.1 b/doc/bash.1 index 9de7f96b..a38d27f7 100644 --- a/doc/bash.1 +++ b/doc/bash.1 @@ -1673,6 +1673,8 @@ If this variable is in the environment when .B bash starts up, the shell enables each option in the list before reading any startup files. +If this variable is exported, child shells will enable each option +in the list. This variable is read-only. .TP .B BASHPID @@ -13259,7 +13261,10 @@ builtin command. .IP \(bu Importing function definitions from the shell environment at startup. .IP \(bu -Parsing the value of +Parsing the values of +.SM +.B BASHOPTS +and .SM .B SHELLOPTS from the shell environment at startup. diff --git a/doc/bashref.texi b/doc/bashref.texi index ef573cec..fdb1047a 100644 --- a/doc/bashref.texi +++ b/doc/bashref.texi @@ -6715,6 +6715,8 @@ as @samp{on} by @samp{shopt}. If this variable is in the environment when Bash starts up, the shell enables each option in the list before reading any startup files. +If this variable is exported, child shells will enable each option +in the list. This variable is readonly. @item BASHPID diff --git a/examples/loadables/Makefile.in b/examples/loadables/Makefile.in index d709a569..2a8d7787 100644 --- a/examples/loadables/Makefile.in +++ b/examples/loadables/Makefile.in @@ -104,7 +104,7 @@ ALLPROG = print truefalse sleep finfo logname basename dirname fdflags \ tty pathchk tee head mkdir rmdir mkfifo mktemp printenv id whoami \ uname sync push ln unlink realpath strftime mypid setpgid seq rm \ accept csv dsv cut stat getconf kv strptime -OTHERPROG = necho hello cat pushd asort +OTHERPROG = necho hello cat pushd asort fltexpr SUBDIRS = perl @@ -250,6 +250,10 @@ stat: stat.o asort: asort.o $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ asort.o $(SHOBJ_LIBS) +fltexpr: fltexpr.o + $(SHOBJ_LD) $(SHOBJ_LDFLAGS) $(SHOBJ_XLDFLAGS) -o $@ fltexpr.o $(SHOBJ_LIBS) -lm + + # pushd is a special case. We use the same source that the builtin version # uses, with special compilation options. # diff --git a/examples/loadables/fltexpr.c b/examples/loadables/fltexpr.c new file mode 100644 index 00000000..f99e3863 --- /dev/null +++ b/examples/loadables/fltexpr.c @@ -0,0 +1,1500 @@ +/* fltexpr.c -- floating-point arithmetic expression evaluation. */ + +/* Copyright (C) 2025 Free Software Foundation, Inc. + + This file is part of GNU Bash, the Bourne Again SHell. + + Bash is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Bash is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Bash. If not, see . +*/ + +/* + All arithmetic is done as double-precision floating point numbers + with some checking for overflow (though division by 0 is caught and + flagged as an error). + + The following operators are handled, grouped into a set of levels in + order of decreasing precedence. + + "id++", "id--" [post-increment and post-decrement] + "++id", "--id" [pre-increment and pre-decrement] + "-", "+" [(unary operators)] + "!" + "**" [(exponentiation)] + "*", "/" + "+", "-" + "<=", ">=", "<", ">" + "==", "!=" + "&&" + "||" + "expr ? expr : expr" + "=", "*=", "/=", "+=", "-=" + , [comma] + + This is a subset of the operators available for integer expressions. + + Sub-expressions within parentheses have a precedence level greater than + all of the above levels and are evaluated first. Within a single prece- + dence group, evaluation is left-to-right, except for the arithmetic + assignment operator (`='), which is evaluated right-to-left (as in C). + + The expression evaluator returns the value of the expression (assignment + statements have as a value what is returned by the RHS). The `fltexpr' + builtin, on the other hand, returns 0 if the last expression evaluates to + a non-zero, and 1 otherwise. + + Implementation is a recursive-descent parser. + + Chet Ramey + chet.ramey@case.edu +*/ + +#include "config.h" + +#include +#include "bashansi.h" + +#if defined (HAVE_UNISTD_H) +# ifdef _MINIX +# include +# endif +# include +#endif + +#include +#include +#include + +#include "chartypes.h" +#include "bashintl.h" + +#include "loadables.h" + +#include "arrayfunc.h" +#include "execute_cmd.h" +#include "flags.h" +#include "subst.h" +#include "typemax.h" /* INTMAX_MAX, INTMAX_MIN */ + +typedef double sh_float_t; + +/* Because of the $((...)) construct, expressions may include newlines. + Here is a macro which accepts newlines, tabs and spaces as whitespace. */ +#define cr_whitespace(c) (whitespace(c) || ((c) == '\n')) + +/* Size be which the expression stack grows when necessary. */ +#define EXPR_STACK_GROW_SIZE 10 + +/* Maximum amount of recursion allowed. This prevents a non-integer + variable such as "num=num+2" from infinitely adding to itself when + "let num=num+2" is given. */ +#define MAX_EXPR_RECURSION_LEVEL 1024 + +/* The Tokens. Singing "The Lion Sleeps Tonight". */ + +#define EQEQ 1 /* "==" */ +#define NEQ 2 /* "!=" */ +#define LEQ 3 /* "<=" */ +#define GEQ 4 /* ">=" */ +#define STR 5 /* string */ +#define NUM 6 /* number */ +#define LAND 7 /* "&&" Logical AND */ +#define LOR 8 /* "||" Logical OR */ +#define OP_ASSIGN 11 /* op= expassign as in Posix.2 */ +#define COND 12 /* exp1 ? exp2 : exp3 */ +#define POWER 13 /* exp1**exp2 */ +#define PREINC 14 /* ++var */ +#define PREDEC 15 /* --var */ +#define POSTINC 16 /* var++ */ +#define POSTDEC 17 /* var-- */ +#define EQ '=' +#define GT '>' +#define LT '<' +#define PLUS '+' +#define MINUS '-' +#define MUL '*' +#define DIV '/' +#define NOT '!' +#define LPAR '(' +#define RPAR ')' +#define QUES '?' +#define COL ':' +#define COMMA ',' + +/* This should be the function corresponding to the operator with the + lowest precedence. */ +#define EXP_LOWEST expcomma + +#define SHFLOAT_STRLEN_BOUND 63 /* For now */ +#define SHFLOAT_BUFSIZE_BOUND (SHFLOAT_STRLEN_BOUND+1) + +/* These are valid when sh_float_t == double */ +#define SHFLOAT_MAX DBL_MAX +#define SHFLOAT_MIN DBL_MIN +#define SHFLOAT_DIG DBL_DIG +#define SHFLOAT_MANT_DIG DBL_MANT_DIG +#define SHFLOAT_LENGTH_MODIFIER 'l'; +#define SHFLOAT_STRTOD strtod + +struct lvalue +{ + char *tokstr; /* possibly-rewritten lvalue if not NULL */ + sh_float_t tokval; /* expression evaluated value */ + SHELL_VAR *tokvar; /* variable described by array or var reference */ + arrayind_t ind; /* array index if not -1 */ +}; + +/* A structure defining a single expression context. */ +typedef struct +{ + int curtok, lasttok; + char *expression, *tp, *lasttp; + sh_float_t tokval; + char *tokstr; + int noeval; + struct lvalue lval; +} FLTEXPR_CONTEXT; + +static char *expression; /* The current expression */ +static char *tp; /* token lexical position */ +static char *lasttp; /* pointer to last token position */ +static int curtok; /* the current token */ +static int lasttok; /* the previous token */ +static int assigntok; /* the OP in OP= */ +static char *tokstr; /* current token string */ +static sh_float_t tokval; /* current token value */ +static int noeval; /* set to 1 if no assignment to be done */ +static procenv_t evalbuf; + +/* set to 1 if the expression has already been run through word expansion */ +static int already_expanded; + +static struct lvalue curlval = {0, 0, 0, -1}; +static struct lvalue lastlval = {0, 0, 0, -1}; + +static sh_float_t nanval, infval; + +static int is_arithop (int); +static int is_multiop (int); +static void readtok (void); /* lexical analyzer */ + +static void init_lvalue (struct lvalue *); +static struct lvalue *alloc_lvalue (void); +static void free_lvalue (struct lvalue *); + +static sh_float_t fltexpr_streval (char *, int, struct lvalue *); +static void evalerror (const char *); + +static sh_float_t fltexpr_strtod (const char *, char **); +static char *fltexpr_format (sh_float_t); + +#if defined (ARRAYS) +static int fltexpr_skipsubscript (char *, char *); +#endif + +static void pushexp (void); +static void popexp (void); +static void fltexpr_unwind (void); +static void fltexpr_bind_variable (char *, char *); +#if defined (ARRAY_VARS) +static void fltexpr_bind_array_element (char *, arrayind_t, char *); +#endif + +static sh_float_t fltexp_subexpr (const char *); + +static sh_float_t expcomma (void); +static sh_float_t expassign (void); +static sh_float_t expcond (void); +static sh_float_t explor (void); +static sh_float_t expland (void); +static sh_float_t expeq (void); +static sh_float_t expcompare (void); +static sh_float_t expshift (void); +static sh_float_t expaddsub (void); +static sh_float_t expmuldiv (void); +static sh_float_t exppower (void); +static sh_float_t expunary (void); +static sh_float_t exp0 (void); + +/* Global var which contains the stack of expression contexts. */ +static FLTEXPR_CONTEXT **expr_stack; +static int expr_depth; /* Location in the stack. */ +static size_t expr_stack_size; /* Number of slots already allocated. */ + +#if defined (ARRAY_VARS) +extern const char * const bash_badsub_errmsg; +#endif + +#define SAVETOK(X) \ + do { \ + (X)->curtok = curtok; \ + (X)->lasttok = lasttok; \ + (X)->tp = tp; \ + (X)->lasttp = lasttp; \ + (X)->tokval = tokval; \ + (X)->tokstr = tokstr; \ + (X)->noeval = noeval; \ + (X)->lval = curlval; \ + } while (0) + +#define RESTORETOK(X) \ + do { \ + curtok = (X)->curtok; \ + lasttok = (X)->lasttok; \ + tp = (X)->tp; \ + lasttp = (X)->lasttp; \ + tokval = (X)->tokval; \ + tokstr = (X)->tokstr; \ + noeval = (X)->noeval; \ + curlval = (X)->lval; \ + } while (0) + +/* Push and save away the contents of the globals describing the + current expression context. */ +static void +pushexp (void) +{ + FLTEXPR_CONTEXT *context; + + if (expr_depth >= MAX_EXPR_RECURSION_LEVEL) + evalerror (_("expression recursion level exceeded")); + + if (expr_depth >= expr_stack_size) + { + expr_stack_size += EXPR_STACK_GROW_SIZE; + expr_stack = (FLTEXPR_CONTEXT **)xrealloc (expr_stack, expr_stack_size * sizeof (FLTEXPR_CONTEXT *)); + } + + context = (FLTEXPR_CONTEXT *)xmalloc (sizeof (FLTEXPR_CONTEXT)); + + context->expression = expression; + SAVETOK(context); + + expr_stack[expr_depth++] = context; +} + +/* Pop the the contents of the expression context stack into the + globals describing the current expression context. */ +static void +popexp (void) +{ + FLTEXPR_CONTEXT *context; + + if (expr_depth <= 0) + { + /* See the comment at the top of evalexp() for an explanation of why + this is done. */ + expression = lasttp = 0; + evalerror (_("recursion stack underflow")); + } + + context = expr_stack[--expr_depth]; + + expression = context->expression; + RESTORETOK (context); + + free (context); +} + +static void +fltexpr_unwind (void) +{ + while (--expr_depth > 0) + { + if (expr_stack[expr_depth]->tokstr) + free (expr_stack[expr_depth]->tokstr); + + if (expr_stack[expr_depth]->expression) + free (expr_stack[expr_depth]->expression); + + free (expr_stack[expr_depth]); + } + if (expr_depth == 0) + free (expr_stack[expr_depth]); /* free the allocated FLTEXPR_CONTEXT */ + + noeval = 0; /* XXX */ +} + +static sh_float_t +fltexpr_strtod (const char *nptr, char **ep) +{ + sh_float_t r; + char *xp; + + errno = 0; + r = SHFLOAT_STRTOD (nptr, &xp); + if (errno == ERANGE) + evalerror ("number out of range"); + else if (r == 0 && *ep == nptr) + evalerror ("invalid number"); + if (ep) + *ep = xp; + return r; +} + +/* Convert from internal format (double) to external format (char *). + Code adapted from gnulib. */ + +static char * +fltexpr_format (sh_float_t val) +{ + int r; + char ret[SHFLOAT_BUFSIZE_BOUND]; /* XXX */ + char format[8], *p; + size_t retsize; + int prec, n; + sh_float_t abs_val; + + abs_val = val < 0 ? -val : val; + + /* There are better ways to do this, but this is an example */ + + /* Construct the format, this is where we tinker */ + p = format; + + *p++ = '%'; + *p++ = '.'; + *p++ = '*'; + *p++ = SHFLOAT_LENGTH_MODIFIER; + *p++ = 'g'; /* XXX */ + *p = '\0'; + + retsize = sizeof (ret); + + /* Use a loop to get the minimal representation but make sure we have the + minimum number of digits required to round-trip a sh_float_t. */ + for (prec = abs_val < SHFLOAT_MIN ? 1 : SHFLOAT_DIG; ; prec++) + { + n = snprintf (ret, retsize, format, prec, val); + if (n < 0 || + prec >= SHFLOAT_MANT_DIG || + (n < retsize && SHFLOAT_STRTOD (ret, NULL) == val)) + break; + } + + return savestring (ret); +} + +static void +fltexpr_bind_variable (char *lhs, char *rhs) +{ + SHELL_VAR *v; + int aflags; + + if (lhs == 0 || *lhs == 0) + return; /* XXX */ + +#if defined (ARRAY_VARS) + aflags = ASS_NOEXPAND|ASS_ALLOWALLSUB; /* allow assoc[@]=value */; +#else + aflags = 0; +#endif + v = builtin_bind_variable (lhs, rhs, aflags); + if (v && ASSIGN_DISALLOWED (v, 0)) + sh_longjmp (evalbuf, 1); /* variable assignment error */ + stupidly_hack_special_variables (lhs); +} + +#if defined (ARRAY_VARS) +/* This is similar to the logic in arrayfunc.c:valid_array_reference when + you pass VA_NOEXPAND. */ +static int +fltexpr_skipsubscript (char *vp, char *cp) +{ + int flags, isassoc, noexp; + SHELL_VAR *entry; + + isassoc = 0; + entry = 0; + + *cp = '\0'; + isassoc = valid_identifier (vp) && (entry = find_variable (vp)) && assoc_p (entry); + *cp = '['; /* ] */ + + /* We're not doing any evaluation here, we should suppress expansion when + skipping over the subscript */ + flags = isassoc ? VA_NOEXPAND : 0; + return (skipsubscript (cp, 0, flags)); +} + +/* Rewrite tok, which is of the form vname[expression], to vname[ind], where + IND is the already-calculated value of expression. */ +static void +fltexpr_bind_array_element (char *tok, arrayind_t ind, char *rhs) +{ + char *lhs, *vname; + size_t llen; + char ibuf[INT_STRLEN_BOUND (arrayind_t) + 1], *istr; + + istr = fmtumax (ind, 10, ibuf, sizeof (ibuf), 0); + vname = array_variable_name (tok, 0, (char **)NULL, (int *)NULL); + + llen = strlen (vname) + sizeof (ibuf) + 3; + lhs = xmalloc (llen); + + sprintf (lhs, "%s[%s]", vname, istr); /* XXX */ + +/*itrace("expr_bind_array_element: %s=%s", lhs, rhs);*/ + fltexpr_bind_variable (lhs, rhs); + free (vname); + free (lhs); +} +#endif /* ARRAY_VARS */ + +/* Evaluate EXPR, and return the arithmetic result. If VALIDP is + non-null, a zero is stored into the location to which it points + if the expression is invalid, non-zero otherwise. If a non-zero + value is returned in *VALIDP, the return value of evalexp() may + be used. + + The `while' loop after the longjmp is caught relies on the above + implementation of pushexp and popexp leaving in expr_stack[0] the + values that the variables had when the program started. That is, + the first things saved are the initial values of the variables that + were assigned at program startup or by the compiler. Therefore, it is + safe to let the loop terminate when expr_depth == 0, without freeing up + any of the expr_depth[0] stuff. */ +sh_float_t +fltexpr_evalexp (const char *expr, int flags, int *validp) +{ + sh_float_t val; + int c; + procenv_t oevalbuf; + + val = 0; + noeval = 0; + already_expanded = (flags&EXP_EXPANDED); + + FASTCOPY (evalbuf, oevalbuf, sizeof (evalbuf)); + + c = setjmp_nosigs (evalbuf); + + if (c) + { + FREE (tokstr); + FREE (expression); + tokstr = expression = (char *)NULL; + + fltexpr_unwind (); + expr_depth = 0; /* XXX - make sure */ + + /* We copy in case we've called evalexp recursively */ + FASTCOPY (oevalbuf, evalbuf, sizeof (evalbuf)); + + if (validp) + *validp = 0; + return (0); + } + + val = fltexp_subexpr (expr); + + if (validp) + *validp = 1; + + FASTCOPY (oevalbuf, evalbuf, sizeof (evalbuf)); + + return (val); +} + +static sh_float_t +fltexp_subexpr (const char *expr) +{ + sh_float_t val; + const char *p; + + for (p = expr; p && *p && cr_whitespace (*p); p++) + ; + + if (p == NULL || *p == '\0') + return (0); + + pushexp (); + expression = savestring (expr); + tp = expression; + + curtok = lasttok = 0; + tokstr = (char *)NULL; + tokval = 0; + init_lvalue (&curlval); + lastlval = curlval; + + readtok (); + + val = EXP_LOWEST (); + + if (curtok != 0) + evalerror (_("arithmetic syntax error in expression")); + + FREE (tokstr); + FREE (expression); + + popexp (); + + return val; +} + +static sh_float_t +expcomma (void) +{ + register sh_float_t value; + + value = expassign (); + while (curtok == COMMA) + { + readtok (); + value = expassign (); + } + + return value; +} + +static sh_float_t +expassign (void) +{ + register sh_float_t value; + char *lhs, *rhs; + arrayind_t lind; +#if defined (HAVE_IMAXDIV) + imaxdiv_t idiv; +#endif + + value = expcond (); + if (curtok == EQ || curtok == OP_ASSIGN) + { + int special, op; + sh_float_t lvalue; + + special = curtok == OP_ASSIGN; + + if (lasttok != STR) + evalerror (_("attempted assignment to non-variable")); + + if (special) + { + op = assigntok; /* a OP= b */ + lvalue = value; + } + + if (tokstr == 0) + evalerror (_("arithmetic syntax error in variable assignment")); + + /* XXX - watch out for pointer aliasing issues here */ + lhs = savestring (tokstr); + /* save ind in case rhs is string var and evaluation overwrites it */ + lind = curlval.ind; + readtok (); + value = expassign (); + + if (special) + { + if (op == DIV && value == 0) + { + if (noeval == 0) + evalerror (_("division by 0")); + else + value = 1; + } + + switch (op) + { + case MUL: + lvalue *= value; + break; + case DIV: + lvalue = lvalue / value; + break; + case PLUS: + lvalue += value; + break; + case MINUS: + lvalue -= value; + break; + default: + free (lhs); + evalerror (_("bug: bad expassign token")); + break; + } + value = lvalue; + } + + rhs = fltexpr_format (value); + if (noeval == 0) + { +#if defined (ARRAY_VARS) + if (lind != -1) + fltexpr_bind_array_element (lhs, lind, rhs); + else +#endif + fltexpr_bind_variable (lhs, rhs); + } + if (curlval.tokstr && curlval.tokstr == tokstr) + init_lvalue (&curlval); + + free (rhs); + free (lhs); + FREE (tokstr); + tokstr = (char *)NULL; /* For freeing on errors. */ + } + + return (value); +} + +/* Conditional expression (expr?expr:expr) */ +static sh_float_t +expcond (void) +{ + sh_float_t cval, val1, val2, rval; + int set_noeval; + + set_noeval = 0; + rval = cval = explor (); + if (curtok == QUES) /* found conditional expr */ + { + if (cval == 0) + { + set_noeval = 1; + noeval++; + } + + readtok (); + if (curtok == 0 || curtok == COL) + evalerror (_("expression expected")); + + val1 = EXP_LOWEST (); + + if (set_noeval) + noeval--; + if (curtok != COL) + evalerror (_("`:' expected for conditional expression")); + + set_noeval = 0; + if (cval) + { + set_noeval = 1; + noeval++; + } + + readtok (); + if (curtok == 0) + evalerror (_("expression expected")); + val2 = expcond (); + + if (set_noeval) + noeval--; + rval = cval ? val1 : val2; + lasttok = COND; + } + return rval; +} + +/* Logical OR. */ +static sh_float_t +explor (void) +{ + register sh_float_t val1, val2; + int set_noeval; + + val1 = expland (); + + while (curtok == LOR) + { + set_noeval = 0; + if (val1 != 0) + { + noeval++; + set_noeval = 1; + } + readtok (); + val2 = expland (); + if (set_noeval) + noeval--; + val1 = val1 || val2; + lasttok = LOR; + } + + return (val1); +} + +/* Logical AND. */ +static sh_float_t +expland (void) +{ + register sh_float_t val1, val2; + int set_noeval; + + val1 = expeq (); /* XXX */ + + while (curtok == LAND) + { + set_noeval = 0; + if (val1 == 0) + { + set_noeval = 1; + noeval++; + } + readtok (); + val2 = expeq (); + if (set_noeval) + noeval--; + val1 = val1 && val2; + lasttok = LAND; + } + + return (val1); +} + +static sh_float_t +expeq (void) +{ + register sh_float_t val1, val2; + + val1 = expcompare (); + + while ((curtok == EQEQ) || (curtok == NEQ)) + { + int op = curtok; + + readtok (); + val2 = expcompare (); + if (op == EQEQ) + val1 = (val1 == val2); + else if (op == NEQ) + val1 = (val1 != val2); + lasttok = NUM; + } + return (val1); +} + +static sh_float_t +expcompare (void) +{ + register sh_float_t val1, val2; + + val1 = expaddsub (); + while ((curtok == LEQ) || + (curtok == GEQ) || + (curtok == LT) || + (curtok == GT)) + { + int op = curtok; + + readtok (); + val2 = expaddsub (); + + if (op == LEQ) + val1 = val1 <= val2; + else if (op == GEQ) + val1 = val1 >= val2; + else if (op == LT) + val1 = val1 < val2; + else /* (op == GT) */ + val1 = val1 > val2; + lasttok = NUM; + } + return (val1); +} + +static sh_float_t +expaddsub (void) +{ + register sh_float_t val1, val2; + + val1 = expmuldiv (); + + while ((curtok == PLUS) || (curtok == MINUS)) + { + int op = curtok; + + readtok (); + val2 = expmuldiv (); + + if (op == PLUS) + val1 += val2; + else if (op == MINUS) + val1 -= val2; + lasttok = NUM; + } + return (val1); +} + +static sh_float_t +expmuldiv (void) +{ + register sh_float_t val1, val2; + + val1 = exppower (); + + while ((curtok == MUL) || (curtok == DIV)) + { + int op = curtok; + char *stp, *sltp; + + stp = tp; + readtok (); + + val2 = exppower (); + + /* Handle division by 0 and twos-complement arithmetic overflow */ + if (op == DIV && val2 == 0) + { + if (noeval == 0) + { + sltp = lasttp; + lasttp = stp; + while (lasttp && *lasttp && whitespace (*lasttp)) + lasttp++; + evalerror (_("division by 0")); + lasttp = sltp; + } + else + val2 = 1; + } + + if (op == MUL) + val1 *= val2; + else if (op == DIV) + val1 = val1 / val2; + lasttok = NUM; + } + return (val1); +} + +static sh_float_t +exppower (void) +{ + register sh_float_t val1, val2; + + val1 = expunary (); + while (curtok == POWER) + { + readtok (); + val2 = exppower (); /* exponentiation is right-associative */ + lasttok = NUM; + if (noeval == 0) + { + if (val2 == 0) + return (1); + val1 = pow (val1, val2); + } + else + val1 = 1; + } + return (val1); +} + +static sh_float_t +expunary (void) +{ + register sh_float_t val; + + if (curtok == NOT) + { + readtok (); + val = !expunary (); + lasttok = NUM; + } + else if (curtok == MINUS) + { + readtok (); + val = - expunary (); + lasttok = NUM; + } + else if (curtok == PLUS) + { + readtok (); + val = expunary (); + lasttok = NUM; + } + else + val = exp0 (); + + return (val); +} + +static sh_float_t +exp0 (void) +{ + sh_float_t val, v2; + char *vincdec; + int stok; + FLTEXPR_CONTEXT ec; + + val = 0; + /* XXX - might need additional logic here to decide whether or not + pre-increment or pre-decrement is legal at this point. */ + if (curtok == PREINC || curtok == PREDEC) + { + stok = lasttok = curtok; + readtok (); + if (curtok != STR) + /* readtok() catches this */ + evalerror (_("identifier expected after pre-increment or pre-decrement")); + + v2 = tokval + ((stok == PREINC) ? 1 : -1); + vincdec = fltexpr_format (v2); + if (noeval == 0) + { +#if defined (ARRAY_VARS) + if (curlval.ind != -1) + fltexpr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else +#endif + if (tokstr) + fltexpr_bind_variable (tokstr, vincdec); + } + free (vincdec); + val = v2; + + curtok = NUM; /* make sure --x=7 is flagged as an error */ + readtok (); + } + else if (curtok == LPAR) + { + /* XXX - save curlval here? Or entire expression context? */ + readtok (); + val = EXP_LOWEST (); + + if (curtok != RPAR) /* ( */ + evalerror (_("missing `)'")); + + /* Skip over closing paren. */ + readtok (); + } + else if (curtok == NUM) + { + val = tokval; + readtok (); + } + else if (curtok == STR) + { + val = tokval; + SAVETOK (&ec); + tokstr = (char *)NULL; /* keep it from being freed */ + noeval = 1; + readtok (); + stok = curtok; + + /* post-increment or post-decrement */ + if (stok == POSTINC || stok == POSTDEC) + { + /* restore certain portions of EC */ + tokstr = ec.tokstr; + noeval = ec.noeval; + curlval = ec.lval; + lasttok = STR; /* ec.curtok */ + + v2 = val + ((stok == POSTINC) ? 1 : -1); + vincdec = fltexpr_format (v2); + if (noeval == 0) + { +#if defined (ARRAY_VARS) + if (curlval.ind != -1) + fltexpr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else +#endif + fltexpr_bind_variable (tokstr, vincdec); + } + free (vincdec); + curtok = NUM; /* make sure x++=7 is flagged as an error */ + } + else + { + /* XXX - watch out for pointer aliasing issues here */ + if (stok == STR) /* free new tokstr before old one is restored */ + FREE (tokstr); + RESTORETOK (&ec); + } + + readtok (); + } + else + evalerror (_("arithmetic syntax error: operand expected")); + + return (val); +} + +static void +init_lvalue (struct lvalue *lv) +{ + lv->tokstr = 0; + lv->tokvar = 0; + lv->tokval = -1; + lv->ind = -1; +} + +static struct lvalue * +alloc_lvalue (void) +{ + struct lvalue *lv; + + lv = xmalloc (sizeof (struct lvalue)); + init_lvalue (lv); + return (lv); +} + +static void +free_lvalue (struct lvalue *lv) +{ + free (lv); /* should be inlined */ +} + +static sh_float_t +fltexpr_streval (char *tok, int e, struct lvalue *lvalue) +{ + SHELL_VAR *v; + char *value; + sh_float_t tval; + int initial_depth; +#if defined (ARRAY_VARS) + arrayind_t ind; + int tflag, aflag; + array_eltstate_t es; +#endif + +/*itrace("fltexpr_streval: %s: noeval = %d expanded=%d", tok, noeval, already_expanded);*/ + /* If we are suppressing evaluation, just short-circuit here instead of + going through the rest of the evaluator. */ + if (noeval) + return (0); + + initial_depth = expr_depth; + +#if defined (ARRAY_VARS) + tflag = AV_NOEXPAND; /* for a start */ +#endif + + /* [[[[[ */ +#if defined (ARRAY_VARS) + aflag = tflag; /* use a different variable for now */ + if (shell_compatibility_level > 51) + aflag |= AV_ATSTARKEYS; + v = (e == ']') ? array_variable_part (tok, tflag, (char **)0, (int *)0) : find_variable (tok); +#else + v = find_variable (tok); +#endif + if (v == 0 && e != ']') + v = find_variable_last_nameref (tok, 0); + + if ((v == 0 || invisible_p (v)) && unbound_vars_is_error) + { +#if defined (ARRAY_VARS) + value = (e == ']') ? array_variable_name (tok, tflag, (char **)0, (int *)0) : tok; +#else + value = tok; +#endif + + set_exit_status (EXECUTION_FAILURE); + err_unboundvar (value); + +#if defined (ARRAY_VARS) + if (e == ']') + FREE (value); /* array_variable_name returns new memory */ +#endif + + if (no_longjmp_on_fatal_error && interactive_shell) + sh_longjmp (evalbuf, 1); + + if (interactive_shell) + { + fltexpr_unwind (); + top_level_cleanup (); + jump_to_top_level (DISCARD); + } + else + jump_to_top_level (FORCE_EOF); + } + +#if defined (ARRAY_VARS) + init_eltstate (&es); + es.ind = -1; + /* If the second argument to get_array_value doesn't include AV_ALLOWALL, + we don't allow references like array[@]. In this case, get_array_value + is just like get_variable_value in that it does not return newly-allocated + memory or quote the results. AFLAG is set above and is either AV_NOEXPAND + or 0. */ + value = (e == ']') ? get_array_value (tok, aflag, &es) : get_variable_value (v); + ind = es.ind; + flush_eltstate (&es); +#else + value = get_variable_value (v); +#endif + + if (expr_depth < initial_depth) + { + if (no_longjmp_on_fatal_error && interactive_shell) + sh_longjmp (evalbuf, 1); + return (0); + } + + tval = (value && *value) ? fltexp_subexpr (value) : 0; + + if (lvalue) + { + lvalue->tokstr = tok; /* XXX */ + lvalue->tokval = tval; + lvalue->tokvar = v; /* XXX */ +#if defined (ARRAY_VARS) + lvalue->ind = ind; +#else + lvalue->ind = -1; +#endif + } + + return (tval); +} + +static inline int +is_multiop (int c) +{ + switch (c) + { + case EQEQ: + case NEQ: + case LEQ: + case GEQ: + case LAND: + case LOR: + case OP_ASSIGN: + case COND: + case POWER: + case PREINC: + case PREDEC: + case POSTINC: + case POSTDEC: + return 1; + default: + return 0; + } +} + +static inline int +is_arithop (int c) +{ + switch (c) + { + case EQ: + case GT: + case LT: + case PLUS: + case MINUS: + case MUL: + case DIV: + case NOT: + case LPAR: + case RPAR: + return 1; /* operator tokens */ + case QUES: + case COL: + case COMMA: + return 1; /* questionable */ + default: + return 0; /* anything else is invalid */ + } +} + +/* Lexical analyzer/token reader for the expression evaluator. Reads the + next token and puts its value into curtok, while advancing past it. + Updates value of tp. May also set tokval (for number) or tokstr (for + string). */ +static void +readtok (void) +{ + char *cp, *xp; + unsigned char c, c1; + int e; + + /* Skip leading whitespace. */ + cp = tp; + c = 0; + e = 0; + while (cp && (c = *cp) && (cr_whitespace (c))) + cp++; + + if (c) + cp++; + + if (c == '\0') + { + lasttok = curtok; + curtok = 0; + tp = cp; + return; + } + lasttp = tp = cp - 1; + + /* check for Inf, Nan here */ + if (strncasecmp (tp, "INF", 3) == 0 && (isalnum (tp[3]) == 0)) + { + cp = tp + 3; + tokval = infval; + lasttok = curtok; + curtok = NUM; + } + else if (strncasecmp (tp, "NAN", 3) == 0 && (isalnum (tp[3]) == 0)) + { + cp = tp + 3; + tokval = nanval; + lasttok = curtok; + curtok = NUM; + } + else if (strncasecmp (tp, "DBL_MIN", 7) == 0 && (isalnum (tp[7]) == 0)) + { + cp = tp + 7; + tokval = SHFLOAT_MIN; + lasttok = curtok; + curtok = NUM; + } + else if (strncasecmp (tp, "DBL_MAX", 7) == 0 && (isalnum (tp[7]) == 0)) + { + cp = tp + 7; + tokval = SHFLOAT_MAX; + lasttok = curtok; + curtok = NUM; + } + else if (legal_variable_starter (c)) + { + /* variable names not preceded with a dollar sign are shell variables. */ + char *savecp; + FLTEXPR_CONTEXT ec; + int peektok; + + while (legal_variable_char (c)) + c = *cp++; + + c = *--cp; + +#if defined (ARRAY_VARS) + if (c == '[') + { + e = fltexpr_skipsubscript (tp, cp); /* XXX - was skipsubscript */ + if (cp[e] == ']') + { + cp += e + 1; + c = *cp; + e = ']'; + } + else + evalerror (_(bash_badsub_errmsg)); + } +#endif /* ARRAY_VARS */ + + *cp = '\0'; + /* XXX - watch out for pointer aliasing issues here */ + if (curlval.tokstr && curlval.tokstr == tokstr) + init_lvalue (&curlval); + + FREE (tokstr); + tokstr = savestring (tp); + *cp = c; + + /* XXX - make peektok part of saved token state? */ + SAVETOK (&ec); + tokstr = (char *)NULL; /* keep it from being freed */ + tp = savecp = cp; + noeval = 1; + curtok = STR; + readtok (); + peektok = curtok; + if (peektok == STR) /* free new tokstr before old one is restored */ + FREE (tokstr); + RESTORETOK (&ec); + cp = savecp; + + /* The tests for PREINC and PREDEC aren't strictly correct, but they + preserve old behavior if a construct like --x=9 is given. */ + if (lasttok == PREINC || lasttok == PREDEC || peektok != EQ) + { + lastlval = curlval; + tokval = fltexpr_streval (tokstr, e, &curlval); + } + else + tokval = 0; + + lasttok = curtok; + curtok = STR; + } + else if (DIGIT(c)) + { + /* Let strtod figure out where to end the floating-point value and let + the parser figure out what's valid. */ + tokval = fltexpr_strtod (tp, &cp); + lasttok = curtok; + curtok = NUM; + } + else + { + c1 = *cp++; + if ((c == EQ) && (c1 == EQ)) + c = EQEQ; + else if ((c == NOT) && (c1 == EQ)) + c = NEQ; + else if ((c == GT) && (c1 == EQ)) + c = GEQ; + else if ((c == LT) && (c1 == EQ)) + c = LEQ; + else if ((c == '*') && (c1 == '*')) + c = POWER; + else if ((c == '-' || c == '+') && c1 == c && curtok == STR) + c = (c == '-') ? POSTDEC : POSTINC; + else if ((c == '-' || c == '+') && c1 == c && curtok == NUM) + { + /* This catches something like --FOO++ */ + if (c == '-') + evalerror (_("--: assignment requires lvalue")); + else + evalerror (_("++: assignment requires lvalue")); + } + else if ((c == '-' || c == '+') && c1 == c) + { + /* Quickly scan forward to see if this is followed by optional + whitespace and an identifier. */ + xp = cp; + while (xp && *xp && cr_whitespace (*xp)) + xp++; + if (legal_variable_starter ((unsigned char)*xp)) + c = (c == '-') ? PREDEC : PREINC; + else + { + /* Posix says unary plus and minus have higher priority than + preinc and predec. */ + /* This catches something like --4++ */ + if (c == '-') + evalerror (_("--: assignment requires lvalue")); + else + evalerror (_("++: assignment requires lvalue")); + } + } + else if (c1 == EQ && member (c, "*/+-")) + { + assigntok = c; /* a OP= b */ + c = OP_ASSIGN; + } + else if (is_arithop (c) == 0) + { + cp--; + /* use curtok, since it hasn't been copied to lasttok yet */ + if (curtok == 0 || is_arithop (curtok) || is_multiop (curtok)) + evalerror (_("arithmetic syntax error: operand expected")); + else + evalerror (_("arithmetic syntax error: invalid arithmetic operator")); + } + else + cp--; /* `unget' the character */ + + /* Should check here to make sure that the current character is one + of the recognized operators and flag an error if not. Could create + a character map the first time through and check it on subsequent + calls. */ + lasttok = curtok; + curtok = c; + } + tp = cp; +} + +static void +evalerror (const char *msg) +{ + char *name, *t; + + name = this_command_name; + for (t = expression; t && whitespace (*t); t++) + ; + internal_error (_("%s%s%s: %s (error token is \"%s\")"), + name ? name : "", name ? ": " : "", + t ? t : "", msg, (lasttp && *lasttp) ? lasttp : ""); + sh_longjmp (evalbuf, 1); +} + +int +fltexpr_builtin (WORD_LIST *list) +{ + sh_float_t ret; + int expok, opt, pflag; + char *str; + + pflag = 0; + + reset_internal_getopt (); + while ((opt = internal_getopt (list, "p")) != -1) + { + switch (opt) + { + case 'p': + pflag = 1; + break; + CASE_HELPOPT; + default: + builtin_usage (); + return (EX_USAGE); + } + } + + list = loptend; + + if (list == 0) + { + builtin_error (_("expression expected")); + return (EXECUTION_FAILURE); + } + + ret = fltexpr_evalexp (list->word->word, EXP_EXPANDED, &expok); + + if (expok == 0) + return (EXECUTION_FAILURE); + + if (pflag) + { + str = fltexpr_format (ret); + printf ("%s\n", str); + free (str); + } + + return ((ret == 0) ? EXECUTION_FAILURE : EXECUTION_SUCCESS); +} + +int +fltexpr_builtin_load (char *s) +{ + /* Internal representations of Inf and NaN here */ + nanval = strtod ("NAN", NULL); + infval = strtod ("INF", NULL); + + return 1; +} + +void +fltexpr_builtin_unload (char *s) +{ +} + +char *fltexpr_doc[] = +{ + "Evaluate floating-point arithmetic expression.", + "", + "Evaluate EXPRESSION as a floating-point arithmetic expression and,", + "if the -p option is supplied, print the value to the standard output.", + "", + "Exit Status:", + "If the EXPRESSION evaluates to 0, the return status is 1; 0 otherwise.", + (char *)NULL +}; + +struct builtin fltexpr_struct = +{ + "fltexpr", /* builtin name */ + fltexpr_builtin, /* function implementing the builtin */ + BUILTIN_ENABLED, /* initial flags for builtin */ + fltexpr_doc, /* array of long documentation strings. */ + "fltexpr [-p] expression", /* usage synopsis; becomes short_doc */ + 0 /* reserved for internal use */ +}; diff --git a/expr.c b/expr.c index 30014aa2..7e527d1f 100644 --- a/expr.c +++ b/expr.c @@ -88,6 +88,9 @@ #include "subst.h" #include "typemax.h" /* INTMAX_MAX, INTMAX_MIN */ +#include "builtins/common.h" /* this_shell_builtin */ +#include "builtins/builtext.h" /* let_builtin */ + /* Because of the $((...)) construct, expressions may include newlines. Here is a macro which accepts newlines, tabs and spaces as whitespace. */ #define cr_whitespace(c) (whitespace(c) || ((c) == '\n')) @@ -194,6 +197,10 @@ static intmax_t expr_streval (char *, int, struct lvalue *); static intmax_t strlong (char *); static void evalerror (const char *); +#if defined (ARRAYS) +static int expr_skipsubscript (char *, char *); +#endif + static void pushexp (void); static void popexp (void); static void expr_unwind (void); @@ -330,7 +337,9 @@ expr_bind_variable (const char *lhs, const char *rhs) return; /* XXX */ #if defined (ARRAY_VARS) - aflags = (array_expand_once && already_expanded) ? ASS_NOEXPAND : 0; + aflags = (array_expand_once && already_expanded) ? ASS_NOEXPAND : 0; /* XXX */ + if (this_shell_builtin == let_builtin && shell_compatibility_level > 51) + aflags |= ASS_NOEXPAND; /* we didn't quote subscripts */ aflags |= ASS_ALLOWALLSUB; /* allow assoc[@]=value */ #else aflags = 0; @@ -347,18 +356,21 @@ expr_bind_variable (const char *lhs, const char *rhs) static int expr_skipsubscript (char *vp, char *cp) { - int flags, isassoc; + int flags, isassoc, noexp; SHELL_VAR *entry; - isassoc = 0; + isassoc = noexp = 0; entry = 0; - if (array_expand_once & already_expanded) + /* We're not doing any evaluation here, we should suppress expansion when + skipping over the subscript */ + noexp = already_expanded && (shell_compatibility_level > 51 || array_expand_once); + if (noexp) { *cp = '\0'; isassoc = valid_identifier (vp) && (entry = find_variable (vp)) && assoc_p (entry); *cp = '['; /* ] */ } - flags = (isassoc && array_expand_once && already_expanded) ? VA_NOEXPAND : 0; + flags = (isassoc && noexp) ? VA_NOEXPAND : 0; return (skipsubscript (cp, 0, flags)); } @@ -881,9 +893,7 @@ expmuldiv (void) val1 = exppower (); - while ((curtok == MUL) || - (curtok == DIV) || - (curtok == MOD)) + while ((curtok == MUL) || (curtok == DIV) || (curtok == MOD)) { int op = curtok; char *stp, *sltp; @@ -1057,47 +1067,49 @@ exp0 (void) /* Skip over closing paren. */ readtok (); } - else if ((curtok == NUM) || (curtok == STR)) + else if (curtok == NUM) { val = tokval; - if (curtok == STR) + readtok (); + } + else if (curtok == STR) + { + val = tokval; + SAVETOK (&ec); + tokstr = (char *)NULL; /* keep it from being freed */ + noeval = 1; + readtok (); + stok = curtok; + + /* post-increment or post-decrement */ + if (stok == POSTINC || stok == POSTDEC) { - SAVETOK (&ec); - tokstr = (char *)NULL; /* keep it from being freed */ - noeval = 1; - readtok (); - stok = curtok; - - /* post-increment or post-decrement */ - if (stok == POSTINC || stok == POSTDEC) - { - /* restore certain portions of EC */ - tokstr = ec.tokstr; - noeval = ec.noeval; - curlval = ec.lval; - lasttok = STR; /* ec.curtok */ - - v2 = val + ((stok == POSTINC) ? 1 : -1); - vincdec = itos (v2); - if (noeval == 0) - { + /* restore certain portions of EC */ + tokstr = ec.tokstr; + noeval = ec.noeval; + curlval = ec.lval; + lasttok = STR; /* ec.curtok */ + + v2 = val + ((stok == POSTINC) ? 1 : -1); + vincdec = itos (v2); + if (noeval == 0) + { #if defined (ARRAY_VARS) - if (curlval.ind != -1) - expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); - else + if (curlval.ind != -1) + expr_bind_array_element (curlval.tokstr, curlval.ind, vincdec); + else #endif - expr_bind_variable (tokstr, vincdec); - } - free (vincdec); - curtok = NUM; /* make sure x++=7 is flagged as an error */ - } - else - { - /* XXX - watch out for pointer aliasing issues here */ - if (stok == STR) /* free new tokstr before old one is restored */ - FREE (tokstr); - RESTORETOK (&ec); - } + expr_bind_variable (tokstr, vincdec); + } + free (vincdec); + curtok = NUM; /* make sure x++=7 is flagged as an error */ + } + else + { + /* XXX - watch out for pointer aliasing issues here */ + if (stok == STR) /* free new tokstr before old one is restored */ + FREE (tokstr); + RESTORETOK (&ec); } readtok (); @@ -1155,6 +1167,8 @@ expr_streval (char *tok, int e, struct lvalue *lvalue) #if defined (ARRAY_VARS) tflag = (array_expand_once && already_expanded) ? AV_NOEXPAND : 0; /* for a start */ + if (this_shell_builtin == let_builtin && shell_compatibility_level > 51) + tflag |= AV_NOEXPAND; /* we didn't quote subscripts */ #endif /* [[[[[ */ diff --git a/lib/readline/complete.c b/lib/readline/complete.c index 473f04db..79026de6 100644 --- a/lib/readline/complete.c +++ b/lib/readline/complete.c @@ -3066,7 +3066,10 @@ _rl_export_completions (char **matches, char *text, int start, int end) fprintf (rl_outstream, "%s\n", text); fprintf (rl_outstream, "%d:%d\n", start, end); /* : because it's not a radix character */ for (i = 0; i < len; i++) - fprintf (rl_outstream, "%s\n", matches[i]); + { + print_filename (matches[i], matches[i], 0); + fprintf (rl_outstream, "\n"); + } fflush (rl_outstream); } diff --git a/lib/readline/doc/history.3 b/lib/readline/doc/history.3 index f6c1479d..53be1201 100644 --- a/lib/readline/doc/history.3 +++ b/lib/readline/doc/history.3 @@ -79,8 +79,8 @@ .SH NAME history \- GNU History Library .SH COPYRIGHT -.if t The GNU History Library is Copyright \(co 1989-2024 by the Free Software Foundation, Inc. -.if n The GNU History Library is Copyright (C) 1989-2024 by the Free Software Foundation, Inc. +.if t The GNU History Library is Copyright \(co 1989-2025 by the Free Software Foundation, Inc. +.if n The GNU History Library is Copyright (C) 1989-2025 by the Free Software Foundation, Inc. .SH DESCRIPTION Many programs read input from the user a line at a time. The GNU diff --git a/lib/readline/doc/history.texi b/lib/readline/doc/history.texi index 247e2081..e78773e2 100644 --- a/lib/readline/doc/history.texi +++ b/lib/readline/doc/history.texi @@ -1,4 +1,4 @@ -\input texinfo @c -*-texinfo-*- +c\input texinfo @c -*-texinfo-*- @c %**start of header (This is for running Texinfo on a region.) @setfilename history.info @settitle GNU History Library @@ -12,7 +12,7 @@ This document describes the GNU History library a programming tool that provides a consistent user interface for recalling lines of previously typed input. -Copyright @copyright{} 1988--2024 Free Software Foundation, Inc. +Copyright @copyright{} 1988--2025 Free Software Foundation, Inc. @quotation Permission is granted to copy, distribute and/or modify this document diff --git a/lib/readline/doc/hstech.texi b/lib/readline/doc/hstech.texi index b9ddd697..82ff77c7 100644 --- a/lib/readline/doc/hstech.texi +++ b/lib/readline/doc/hstech.texi @@ -1,7 +1,7 @@ @ignore This file documents the user interface to the GNU History library. -Copyright (C) 1988-2024 Free Software Foundation, Inc. +Copyright (C) 1988-2025 Free Software Foundation, Inc. Authored by Brian Fox and Chet Ramey. Permission is granted to make and distribute verbatim copies of this manual diff --git a/lib/readline/doc/hsuser.texi b/lib/readline/doc/hsuser.texi index 04b25d14..a53d5575 100644 --- a/lib/readline/doc/hsuser.texi +++ b/lib/readline/doc/hsuser.texi @@ -1,7 +1,7 @@ @ignore This file documents the user interface to the GNU History library. -Copyright (C) 1988--2024 Free Software Foundation, Inc. +Copyright (C) 1988--2025 Free Software Foundation, Inc. Authored by Brian Fox and Chet Ramey. Permission is granted to make and distribute verbatim copies of this manual diff --git a/lib/readline/doc/readline.3 b/lib/readline/doc/readline.3 index 40a11b8d..3c1ef483 100644 --- a/lib/readline/doc/readline.3 +++ b/lib/readline/doc/readline.3 @@ -60,8 +60,8 @@ readline \- get a line from a user with editing \fBreadline\fP (\fIconst char *prompt\fP); .fi .SH COPYRIGHT -.if n Readline is Copyright (C) 1989\-2024 Free Software Foundation, Inc. -.if t Readline is Copyright \(co 1989\-2024 Free Software Foundation, Inc. +.if n Readline is Copyright (C) 1989\-2025 Free Software Foundation, Inc. +.if t Readline is Copyright \(co 1989\-2025 Free Software Foundation, Inc. .SH DESCRIPTION .LP .B readline diff --git a/lib/readline/doc/rlman.texi b/lib/readline/doc/rlman.texi index d016c762..90497093 100644 --- a/lib/readline/doc/rlman.texi +++ b/lib/readline/doc/rlman.texi @@ -13,7 +13,7 @@ This manual describes the GNU Readline Library consistency of user interface across discrete programs which provide a command line interface. -Copyright @copyright{} 1988--2024 Free Software Foundation, Inc. +Copyright @copyright{} 1988--2025 Free Software Foundation, Inc. @quotation Permission is granted to copy, distribute and/or modify this document diff --git a/lib/readline/doc/rltech.texi b/lib/readline/doc/rltech.texi index a1c4211c..c8965ef6 100644 --- a/lib/readline/doc/rltech.texi +++ b/lib/readline/doc/rltech.texi @@ -7,7 +7,7 @@ This document describes the GNU Readline Library, a utility for aiding in the consistency of user interface across discrete programs that need to provide a command line interface. -Copyright (C) 1988--2024 Free Software Foundation, Inc. +Copyright (C) 1988--2025 Free Software Foundation, Inc. Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice diff --git a/lib/readline/doc/rluser.texi b/lib/readline/doc/rluser.texi index 5eff0ff2..27078b5e 100644 --- a/lib/readline/doc/rluser.texi +++ b/lib/readline/doc/rluser.texi @@ -11,7 +11,7 @@ use these features. There is a document entitled "readline.texinfo" which contains both end-user and programmer documentation for the GNU Readline Library. -Copyright (C) 1988--2024 Free Software Foundation, Inc. +Copyright (C) 1988--2025 Free Software Foundation, Inc. Authored by Brian Fox and Chet Ramey. diff --git a/lib/readline/doc/rluserman.texi b/lib/readline/doc/rluserman.texi index 51255cac..6265c63c 100644 --- a/lib/readline/doc/rluserman.texi +++ b/lib/readline/doc/rluserman.texi @@ -12,7 +12,7 @@ This manual describes the end user interface of the GNU Readline Library consistency of user interface across discrete programs which provide a command line interface. -Copyright @copyright{} 1988--2024 Free Software Foundation, Inc. +Copyright @copyright{} 1988--2025 Free Software Foundation, Inc. @quotation Permission is granted to copy, distribute and/or modify this document diff --git a/lib/readline/doc/version.texi b/lib/readline/doc/version.texi index 1bd3e3db..9faa3869 100644 --- a/lib/readline/doc/version.texi +++ b/lib/readline/doc/version.texi @@ -1,5 +1,5 @@ @ignore -Copyright (C) 1988-2024 Free Software Foundation, Inc. +Copyright (C) 1988-2025 Free Software Foundation, Inc. @end ignore @set EDITION 8.3 diff --git a/shell.c b/shell.c index b0010f06..df231132 100644 --- a/shell.c +++ b/shell.c @@ -1,6 +1,6 @@ /* shell.c -- GNU's idea of the POSIX shell specification. */ -/* Copyright (C) 1987-2024 Free Software Foundation, Inc. +/* Copyright (C) 1987-2025 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. diff --git a/subst.c b/subst.c index 96222b66..5c0cd844 100644 --- a/subst.c +++ b/subst.c @@ -4,7 +4,7 @@ /* ``Have a little faith, there's magic in the night. You ain't a beauty, but, hey, you're alright.'' */ -/* Copyright (C) 1987-2024 Free Software Foundation, Inc. +/* Copyright (C) 1987-2025 Free Software Foundation, Inc. This file is part of GNU Bash, the Bourne Again SHell. @@ -1184,7 +1184,9 @@ string_extract_verbatim (const char *string, size_t slen, size_t *sindex, char * #endif if ((flags & SX_NOCTLESC) == 0 && c == CTLESC) { - i += 2; + i++; + CHECK_STRING_OVERRUN (i, i, slen, c); + ADVANCE_CHAR (string, slen, i); /* CTLESC can quote mbchars */ CHECK_STRING_OVERRUN (i, i, slen, c); continue; } -- 2.47.3