]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
fix issue with internally quoting multibyte characters; the let builtin now suppresse...
authorChet Ramey <chet.ramey@case.edu>
Fri, 17 Jan 2025 16:41:49 +0000 (11:41 -0500)
committerChet Ramey <chet.ramey@case.edu>
Fri, 17 Jan 2025 16:41:49 +0000 (11:41 -0500)
22 files changed:
CHANGES
CWRU/CWRU.chlog
MANIFEST
builtins/printf.def
doc/bash.1
doc/bashref.texi
examples/loadables/Makefile.in
examples/loadables/fltexpr.c [new file with mode: 0644]
expr.c
lib/readline/complete.c
lib/readline/doc/history.3
lib/readline/doc/history.texi
lib/readline/doc/hstech.texi
lib/readline/doc/hsuser.texi
lib/readline/doc/readline.3
lib/readline/doc/rlman.texi
lib/readline/doc/rltech.texi
lib/readline/doc/rluser.texi
lib/readline/doc/rluserman.texi
lib/readline/doc/version.texi
shell.c
subst.c

diff --git a/CHANGES b/CHANGES
index 16cd0e3510cda76a48a5a1d1614961c38b01b754..6b742da2232d2ffbd24f1248d2f4213dd9ebfdde 100644 (file)
--- 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).
 
index d5c1792e5060ce88f51975e4b7e360b3358636b1..dfcc99970eafb3da0c9695c4ea4e7df000d507b3 100644 (file)
@@ -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 <martin@kurahaupo.gen.nz>
+
+                                   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 <greg@wooledge.org>
+
+                                  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 <matthewktromp@gmail.com>
+
+                                  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
index 48c7a6c4672c1cfdea92ecd3f3e42f5451f8c51f..72d684e3344be17d345da4e2c2f91714fface655 100644 (file)
--- 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
index 0ba0e634204c97805dd6a125549a035f7ff9ddf6..5321b57c781971f75f4f8413e9943a2fdbb18d82 100644 (file)
@@ -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);
 }
 
index 9de7f96be7ff2a0c1d119a91d5616cd34e4df2b4..a38d27f7490df8cc126b667b56d92f0c201d308e 100644 (file)
@@ -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.
index ef573ceca7f186da5713c3e5a50d6982683572c0..fdb1047a68122e587a803a648278b8aae23c1e7f 100644 (file)
@@ -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
index d709a569cd4692b18299747e8c3a616e3036ad5a..2a8d77876d94a710a26b5898621807024a315635 100644 (file)
@@ -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 (file)
index 0000000..f99e386
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.
+*/
+
+/*
+ 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 <stdio.h>
+#include "bashansi.h"
+
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include <errno.h>
+#include <float.h>
+#include <math.h>
+
+#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 30014aa24c03fb168f4a2170d698638aea94dccd..7e527d1fa785a02e670fd9e8c21e4677beec5ba9 100644 (file)
--- 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
 
   /* [[[[[ */
index 473f04db5b93c6adb3b7d9dff347497b505d9a5a..79026de6f8c389bb577423be2f2ff1a8e0beefcf 100644 (file)
@@ -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);
 }
 
index f6c1479d18a71883f5bc8d533b3562a8d15bc4e3..53be12014358a2e4ab808860b449070d9f40aa55 100644 (file)
@@ -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
index 247e2081303380eda7e2d56daf55568487a337ec..e78773e2a4b2a91a857b6faab73bf50f6fb3b34c 100644 (file)
@@ -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
index b9ddd69703790710a7e224ee61ea2fdc9b0e71c5..82ff77c7a9a076569a705c1a86fd8fecad159161 100644 (file)
@@ -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
index 04b25d1437145348765d2c63a290b8aa43e04dfc..a53d55758cbfee926490c1d737a2ef188d24bee2 100644 (file)
@@ -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
index 40a11b8dda389e6a228d3faa05b73e89e9baf8b1..3c1ef4838062772d7f14b5491b3847a438612635 100644 (file)
@@ -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
index d016c76259a71b724c8cde16c9cb33fdf1a980c6..904970935cf92e6738f55ec8fd1d986e8a2051e3 100644 (file)
@@ -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
index a1c4211c9b3229fb029f9c938fd7633edfc34fd1..c8965ef640499e9b40fe1442dfec8166cd532d79 100644 (file)
@@ -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
index 5eff0ff2c6a4707751d910c1824e1abe3ec07959..27078b5e01324379c6aa042a6fae1bc5872eda02 100644 (file)
@@ -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.
 
index 51255cacbc6d503d359c4a84bf46d8005ea6e7a0..6265c63c5d632b0265c2f40c0cdcd21388aba897 100644 (file)
@@ -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
index 1bd3e3db11345401bfc6d08a58733f4bc5b92114..9faa386904d9d848392c5c088d0138ffb63b904b 100644 (file)
@@ -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 b0010f0629c550ce1f3eb0929928994f4f371475..df2311323c22a844f9a0557ab3d84bf498501b24 100644 (file)
--- 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 96222b6675558464690b0643df93c45ac9ca9613..5c0cd8448e8df280c134d47861c795600c4d7b06 100644 (file)
--- 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;
        }