]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
commit bash-20050922 snapshot
authorChet Ramey <chet.ramey@case.edu>
Sat, 3 Dec 2011 18:51:24 +0000 (13:51 -0500)
committerChet Ramey <chet.ramey@case.edu>
Sat, 3 Dec 2011 18:51:24 +0000 (13:51 -0500)
29 files changed:
CHANGES
CWRU/CWRU.chlog
CWRU/CWRU.chlog~
MANIFEST
MANIFEST~
Makefile.in
autom4te.cache/requests
builtins/history.def
builtins/history.def~
configure
doc/bash.1
doc/version.texi
examples/bashdb/README [new file with mode: 0644]
examples/bashdb/bashdb
examples/bashdb/bashdb~ [new file with mode: 0755]
lib/malloc/Makefile.in
lib/readline/chardefs.h
lib/readline/chardefs.h~ [new file with mode: 0644]
lib/readline/doc/readline.3
lib/readline/doc/version.texi
lib/readline/isearch.c
lib/readline/isearch.c~
lib/readline/rlmbutil.h
lib/readline/rlmbutil.h~
lib/readline/text.c
lib/readline/text.c~
parse.y
parse.y.save1 [new file with mode: 0644]
subst.c

diff --git a/CHANGES b/CHANGES
index 941cd695921e8ff1aabbe9c662fd9b634b0a0e7d..e00e9648614eff3946a58ab0b750d803c626fa86 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -63,6 +63,14 @@ t.  Fixed a bug that caused the expanded value of a $'...' string to be
     incorrectly re-quoted if it occurred within a double-quoted ${...}
     parameter expansion.
 
+u.  Restored default emacs-mode key binding of M-TAB to dynamic-complete-history.
+
+v.  Fixed a bug that caused core dumps when interrupting loops running builtins
+    on some systems.
+
+w.  Make sure that some of the functions bash provides replacements for are
+    not cpp defines.
+
 2.  Changes to Readline
 
 a.  The `change-case' command now correctly changes the case of multibyte
@@ -87,6 +95,9 @@ a.  A new configuration option, `--enable-strict-posix-default', which will
 
 4.  New Features in Readline
 
+a.  If the rl_completion_query_items is set to a value < 0, readline never
+    asks the user whether or not to view the possible completions.
+
 ------------------------------------------------------------------------------
 This document details the changes between this version, bash-3.1-alpha1,
 and the previous version, bash-3.0-release.
index f5c90cba2cb3a45631f91ba27e0c6bcbc5d38b5b..1a892cf034d8441f2e400b7899e3d91a48917ab8 100644 (file)
@@ -12118,3 +12118,37 @@ eval.c
 lib/sh/strto[iu]max.c
        - make sure the function being declared is not a cpp define before
          defining it -- should fix problems on HP-UX
+
+                                  9/19
+                                  ----
+Makefile.in
+       - make sure the binaries for the tests are at the front of $PATH
+
+                                  9/22
+                                  ----
+parse.y
+       - new flag for parse_matched_pair: P_COMMAND, indicating that the
+         text being parsed is a command (`...`, $(...))
+       - change calls to parse_matched_pair to include P_COMMAND where
+         appropriate
+       - if P_COMMAND flag is set and the text is unquoted, check for comments
+         and don't try to parse embedded quoted strings if in a comment (still
+         not exactly right yet)
+
+                                  9/24
+                                  ----
+builtins/history.def
+       - if running history -n, don't count these new lines as history lines
+         for the current session if the `histappend' shell option is set.
+         If we're just appending to the history file, the issue that caused
+         history_lines_this_session to be recalculated doesn't apply -- the
+         history file won't be missing any entries
+
+lib/readline/isearch.c
+       - fix C-w handler for isearch string reader to handle multibyte chars
+
+lib/readline/rlmbutil.h
+       - new defines for _rl_to_wupper and _rl_to_wlower
+
+lib/readline/text.c
+       - use _rl_to_wupper and _rl_to_wlower as appropriate
index 0e7219f4c696b5822775207a497d14499e810ad3..2ab04f82d7ecd487a18a1ba4f423e873f0704a4d 100644 (file)
@@ -12112,3 +12112,37 @@ eval.c
          longjmp might restore the value of current_command after we've
          disposed it, and the subsequent call to dispose_command from the
          DISCARD case will free memory twice
+
+                                  9/16
+                                  ----
+lib/sh/strto[iu]max.c
+       - make sure the function being declared is not a cpp define before
+         defining it -- should fix problems on HP-UX
+
+                                  9/19
+                                  ----
+Makefile.in
+       - make sure the binaries for the tests are at the front of $PATH
+
+                                  9/22
+                                  ----
+parse.y
+       - new flag for parse_matched_pair: P_COMMAND, indicating that the
+         text being parsed is a command (`...`, $(...))
+       - change calls to parse_matched_pair to include P_COMMAND where
+         appropriate
+       - if P_COMMAND flag is set and the text is unquoted, check for comments
+         and don't try to parse embedded quoted strings if in a comment (still
+         not exactly right yet)
+
+                                  9/24
+                                  ----
+builtins/history.def
+       - if running history -n, don't count these new lines as history lines
+         for the current session if the `histappend' shell option is set.
+         If we're just appending to the history file, the issue that caused
+         history_lines_this_session to be recalculated doesn't apply -- the
+         history file won't be missing any entries
+
+lib/readline/isearch.c
+       - fix C-w handler for isearch string reader to handle multibyte chars
index 777b36038a9e0d2e7db3ff87491a0a4691c09a95..a9cacb56e011a2042a8eecdc70dbe88d692a3743 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -526,6 +526,7 @@ support/xenix-link.sh       f       755
 support/shobj-conf     f       755
 support/rlvers.sh      f       755
 examples/bashdb/PERMISSION     f
+examples/bashdb/README f
 examples/bashdb/bashdb         f
 examples/bashdb/bashdb.el      f
 examples/obashdb/PERMISSION    f
index dbc1ec74e7c22fb0420436467252615b2247598d..777b36038a9e0d2e7db3ff87491a0a4691c09a95 100644 (file)
--- a/MANIFEST~
+++ b/MANIFEST~
@@ -714,6 +714,7 @@ tests/dollar-at-star        f
 tests/dollar-at1.sub   f
 tests/dollar-at2.sub   f
 tests/dollar-star1.sub f
+tests/dollar-star2.sub f
 tests/dollar.right     f
 tests/dstack.tests     f
 tests/dstack.right     f
@@ -766,6 +767,8 @@ tests/history.right f
 tests/history.list     f       444
 tests/ifs.tests                f
 tests/ifs.right                f
+tests/ifs-posix.tests  f
+tests/ifs-posix.right  f
 tests/input-line.sh    f
 tests/input-line.sub   f
 tests/input.right      f
@@ -863,6 +866,7 @@ tests/run-herestr   f
 tests/run-histexpand   f
 tests/run-history      f
 tests/run-ifs          f
+tests/run-ifs-posix    f
 tests/run-input-test   f
 tests/run-intl         f
 tests/run-iquote       f
index 734e2e89b0746d75db0b075d3398f471a1032f73..0958adc57d93dddbffa2cab16a9904dde1051027 100644 (file)
@@ -794,7 +794,7 @@ test tests check:   force $(Program) $(TESTS_SUPPORT)
        @-test -d tests || mkdir tests
        @cp $(TESTS_SUPPORT) tests
        @( cd $(srcdir)/tests && \
-               PATH=$$PATH:$(BUILD_DIR)/tests THIS_SH=$(THIS_SH) $(SHELL) ${TESTSCRIPT} )
+               PATH=$(BUILD_DIR)/tests:$$PATH THIS_SH=$(THIS_SH) $(SHELL) ${TESTSCRIPT} )
 
 symlinks:
        $(SHELL) $(SUPPORT_SRC)fixlinks -s $(srcdir)
index baa7f90df4a5f72ac75d0295aabb043a16b88b7a..84e4075e3c65875aa1f7dcc2ad2003258d75c6bc 100644 (file)
                       {
                         'm4_pattern_forbid' => 1,
                         'AC_CONFIG_LIBOBJ_DIR' => 1,
-                        'AC_TYPE_OFF_T' => 1,
                         'AC_C_VOLATILE' => 1,
+                        'AC_TYPE_OFF_T' => 1,
                         'AC_FUNC_CLOSEDIR_VOID' => 1,
                         'AC_REPLACE_FNMATCH' => 1,
                         'AC_PROG_LIBTOOL' => 1,
                         'AC_FUNC_STAT' => 1,
-                        'AC_FUNC_WAIT3' => 1,
                         'AC_HEADER_TIME' => 1,
-                        'AM_AUTOMAKE_VERSION' => 1,
-                        'AC_STRUCT_TM' => 1,
+                        'AC_FUNC_WAIT3' => 1,
                         'AC_FUNC_LSTAT' => 1,
-                        'AC_FUNC_GETMNTENT' => 1,
+                        'AC_STRUCT_TM' => 1,
+                        'AM_AUTOMAKE_VERSION' => 1,
                         'AC_TYPE_MODE_T' => 1,
+                        'AC_FUNC_GETMNTENT' => 1,
                         'AC_FUNC_STRTOD' => 1,
                         'AC_CHECK_HEADERS' => 1,
                         'AC_FUNC_STRNLEN' => 1,
                         'AC_STRUCT_ST_BLOCKS' => 1,
                         'AC_TYPE_SIGNAL' => 1,
                         'AC_TYPE_UID_T' => 1,
-                        'AC_PROG_MAKE_SET' => 1,
                         'AC_CONFIG_AUX_DIR' => 1,
-                        'm4_pattern_allow' => 1,
+                        'AC_PROG_MAKE_SET' => 1,
                         'sinclude' => 1,
+                        'm4_pattern_allow' => 1,
                         'AC_DEFINE_TRACE_LITERAL' => 1,
                         'AC_FUNC_STRERROR_R' => 1,
                         'AC_PROG_CC' => 1,
-                        'AC_DECL_SYS_SIGLIST' => 1,
                         'AC_FUNC_FORK' => 1,
-                        'AC_FUNC_STRCOLL' => 1,
+                        'AC_DECL_SYS_SIGLIST' => 1,
                         'AC_FUNC_VPRINTF' => 1,
+                        'AC_FUNC_STRCOLL' => 1,
                         'AC_PROG_YACC' => 1,
                         'AC_INIT' => 1,
                         'AC_STRUCT_TIMEZONE' => 1,
                         'AM_MAINTAINER_MODE' => 1,
                         'AC_FUNC_UTIME_NULL' => 1,
                         'AC_FUNC_SELECT_ARGTYPES' => 1,
-                        'AC_HEADER_STAT' => 1,
                         'AC_FUNC_STRFTIME' => 1,
-                        'AC_C_INLINE' => 1,
+                        'AC_HEADER_STAT' => 1,
                         'AC_PROG_CPP' => 1,
-                        'AC_C_CONST' => 1,
-                        'AC_PROG_LEX' => 1,
+                        'AC_C_INLINE' => 1,
                         'AC_TYPE_PID_T' => 1,
+                        'AC_PROG_LEX' => 1,
+                        'AC_C_CONST' => 1,
                         'AC_CONFIG_FILES' => 1,
                         'include' => 1,
                         'AC_FUNC_SETVBUF_REVERSED' => 1,
                         'AC_PROG_INSTALL' => 1,
                         'AM_GNU_GETTEXT' => 1,
-                        'AC_FUNC_OBSTACK' => 1,
                         'AC_CHECK_LIB' => 1,
+                        'AC_FUNC_OBSTACK' => 1,
                         'AC_FUNC_MALLOC' => 1,
                         'AC_FUNC_GETGROUPS' => 1,
                         'AC_FUNC_GETLOADAVG' => 1,
                         'AH_OUTPUT' => 1,
                         'AC_FUNC_FSEEKO' => 1,
                         'AM_PROG_CC_C_O' => 1,
-                        'AM_CONDITIONAL' => 1,
-                        'AC_CANONICAL_SYSTEM' => 1,
                         'AC_FUNC_MKTIME' => 1,
+                        'AC_CANONICAL_SYSTEM' => 1,
+                        'AM_CONDITIONAL' => 1,
                         'AC_CONFIG_HEADERS' => 1,
                         'AC_HEADER_SYS_WAIT' => 1,
-                        'AC_PROG_LN_S' => 1,
                         'AC_FUNC_MEMCMP' => 1,
+                        'AC_PROG_LN_S' => 1,
                         'm4_include' => 1,
                         'AC_HEADER_DIRENT' => 1,
                         'AC_CHECK_FUNCS' => 1
index 0086b0bfafd9b0f6f5b21296a03a76a704fdfeb5..efee005210c490bee46bfc5aa6436c9faa0b9ccb 100644 (file)
@@ -77,6 +77,7 @@ extern int errno;
 #endif
 
 extern int current_command_line_count;
+extern int force_append_history;       /* shopt -s histappend */
 
 int delete_last_history __P((void));
 
@@ -215,15 +216,19 @@ history_builtin (list)
       using_history ();
 
       history_lines_in_file = where_history ();
-      /* The question is whether we reset history_lines_this_session to 0,
-        losing any history entries we had before we read the new entries
-        from the history file, or whether we count the new entries we just
-        read from the file as history lines added during this session.
+
+      /* If we're rewriting the history file at shell exit rather than just
+        appending the lines from this session to it, the question is whether
+        we reset history_lines_this_session to 0, losing any history entries
+        we had before we read the new entries from the history file, or
+        whether we count the new entries we just read from the file as
+        history lines added during this session.
         Right now, we do the latter.  This will cause these history entries
         to be written to the history file along with any intermediate entries
         we add when we do a `history -a', but the alternative is losing
         them altogether. */
-      history_lines_this_session += history_lines_in_file - old_history_lines +
+      if (force_append_history == 0)
+       history_lines_this_session += history_lines_in_file - old_history_lines +
                                    history_base - obase;
     }
 
index 33164a38f0e5df990fd4f32a7d43b1b619d24216..57d721df9b1b4d6fdd6b346bdfb2f7261d2af62b 100644 (file)
@@ -78,10 +78,11 @@ extern int errno;
 
 extern int current_command_line_count;
 
+int delete_last_history __P((void));
+
 static char *histtime __P((HIST_ENTRY *, const char *));
 static void display_history __P((WORD_LIST *));
 static int delete_histent __P((int));
-static int delete_last_history __P((void));
 static void push_history __P((WORD_LIST *));
 static int expand_and_print_history __P((WORD_LIST *));
 
@@ -214,15 +215,19 @@ history_builtin (list)
       using_history ();
 
       history_lines_in_file = where_history ();
-      /* The question is whether we reset history_lines_this_session to 0,
-        losing any history entries we had before we read the new entries
-        from the history file, or whether we count the new entries we just
-        read from the file as history lines added during this session.
+
+      /* If we're rewriting the history file at shell exit rather than just
+        appending the lines from this session to it, the question is whether
+        we reset history_lines_this_session to 0, losing any history entries
+        we had before we read the new entries from the history file, or
+        whether we count the new entries we just read from the file as
+        history lines added during this session.
         Right now, we do the latter.  This will cause these history entries
         to be written to the history file along with any intermediate entries
         we add when we do a `history -a', but the alternative is losing
         them altogether. */
-      history_lines_this_session += history_lines_in_file - old_history_lines +
+      if (force_append_history == 0)
+       history_lines_this_session += history_lines_in_file - old_history_lines +
                                    history_base - obase;
     }
 
@@ -310,7 +315,7 @@ delete_histent (i)
   return 1;
 }
 
-static int
+int
 delete_last_history ()
 {
   register int i;
@@ -346,8 +351,6 @@ push_history (list)
 {
   char *s;
 
-if (current_command_line_count == 0)
-  itrace("push_history: current_command_line_count == 0");
   /* Delete the last history entry if it was a single entry added to the
      history list (generally the `history -s' itself), or if `history -s'
      is being used in a compound command and the compound command was
@@ -385,7 +388,7 @@ expand_and_print_history (list)
   char *s;
   int r, result;
 
-  if (hist_last_line_added && delete_last_history () == 0)
+  if (hist_last_line_pushed == 0 && hist_last_line_added && delete_last_history () == 0)
     return EXECUTION_FAILURE;
   result = EXECUTION_SUCCESS;
   while (list)
index 268791bd2712c837f09a47fb34704d69982b03e0..5ab8176c4f79e476db633d5a440b35ff0b74dda5 100755 (executable)
--- a/configure
+++ b/configure
@@ -28319,4 +28319,3 @@ if test "$no_create" != yes; then
   # would make configure fail if this is the last instruction.
   $ac_cs_success || { (exit 1); exit 1; }
 fi
-
index 27613849dcbbc4e27a2146bafc2ca273221dcca6..86a5e676b81a67a3c7ce4cc2d77c711029367871 100644 (file)
@@ -11,7 +11,7 @@
 .\" bash_builtins, strip all but Built-Ins section
 .if \n(zZ=1 .ig zZ
 .if \n(zY=1 .ig zY
-.TH BASH 1 "2005 Aug 27" "GNU Bash-3.1-alpha1"
+.TH BASH 1 "2005 Aug 27" "GNU Bash-3.1-beta1"
 .\"
 .\" There's some problem with having a `@'
 .\" in a tagged paragraph with the BSD man macros.
index c42e5aadd1f2e017c0b17d1d93ca1d6b824f09e0..f769a19182095c59e40d82ed810b03a36548fdfe 100644 (file)
@@ -4,7 +4,7 @@ Copyright (C) 1988-2005 Free Software Foundation, Inc.
 
 @set LASTCHANGE Mon Sep  5 11:47:04 EDT 2005
 
-@set EDITION 3.1-alpha1
-@set VERSION 3.1-alpha1
+@set EDITION 3.1-beta1
+@set VERSION 3.1-beta1
 @set UPDATED 5 September 2005
 @set UPDATED-MONTH September 2005
diff --git a/examples/bashdb/README b/examples/bashdb/README
new file mode 100644 (file)
index 0000000..2f643d1
--- /dev/null
@@ -0,0 +1,3 @@
+This is a sample implementation of a bash debugger.  It is not the same
+as the project available from http://bashdb.sourceforge.net, and has been
+deprecated in favor of that implementation.
index 2bca9f96d19192988b7c516f85004c4c139ad026..560cb7cc749855f635f5c77a2718c1db3943e442 100755 (executable)
@@ -509,11 +509,11 @@ function _showline
   fi
 
   if (( $line < 100 )); then
-    _msg "$_guineapig:$line   $bp $cl${_lines[$line]}"
+    _msg "${_guineapig/*\//}:$line   $bp $cl${_lines[$line]}"
   elif (( $line < 10 )); then
-    _msg "$_guineapig:$line  $bp $cl${_lines[$line]}"
+    _msg "${_guineapig/*\//}:$line  $bp $cl${_lines[$line]}"
   elif (( $line > 0 )); then
-    _msg "$_guineapig:$line $bp $cl${_lines[$line]}"
+    _msg "${_guineapig/*\//}:$line $bp $cl${_lines[$line]}"
   fi
 }
 
@@ -564,7 +564,7 @@ let _trace=0
 let _i=1
 
 # Be careful about quoted newlines
-_potbelliedpig=${TMPDIR-/tmp}/$_guineapig.$$
+_potbelliedpig=${TMPDIR-/tmp}/${_guineapig/*\//}.$$
 sed 's,\\$,\\\\,' $_guineapig > $_potbelliedpig
 
 _msg "Reading source from file: $_guineapig"
diff --git a/examples/bashdb/bashdb~ b/examples/bashdb/bashdb~
new file mode 100755 (executable)
index 0000000..2bca9f9
--- /dev/null
@@ -0,0 +1,581 @@
+#! /bin/bash
+# bashdb - Bash shell debugger
+#
+# Adapted from an idea in O'Reilly's `Learning the Korn Shell'
+# Copyright (C) 1993-1994 O'Reilly and Associates, Inc.
+# Copyright (C) 1998, 1999, 2001 Gary V. Vaughan <gvv@techie.com>>
+#
+# This program 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 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# NOTE:
+#
+# This program requires bash 2.x.
+# If bash 2.x is installed as "bash2", you can invoke  bashdb like this:
+#
+#   DEBUG_SHELL=/bin/bash2 /bin/bash2 bashdb script.sh
+
+# TODO:
+#
+# break [regexp]
+# cond [break] [condition]
+# tbreak [regexp|+lines]
+# restart
+# Variable watchpoints
+# Instrument `source' and `.' files in $_potbelliedpig
+# be cleverer about lines we allow breakpoints to be set on
+# break [function_name]
+
+echo 'Bash Debugger version 1.2.4'
+
+export _dbname=${0##*/}
+
+if test $# -lt 1; then
+  echo "$_dbname: Usage: $_dbname filename" >&2
+  exit 1
+fi
+
+_guineapig=$1
+
+if test ! -r $1; then
+  echo "$_dbname: Cannot read file '$_guineapig'." >&2
+  exit 1
+fi
+
+shift
+
+__debug=${TMPDIR-/tmp}/bashdb.$$
+sed -e '/^# bashdb - Bash shell debugger/,/^# -- DO NOT DELETE THIS LINE -- /d' "$0" > $__debug
+cat $_guineapig >> $__debug
+exec ${DEBUG_SHELL-bash} $__debug $_guineapig "$@"
+
+exit 1
+
+# -- DO NOT DELETE THIS LINE -- The program depends on it
+
+#bashdb preamble
+# $1 name of the original guinea pig script
+
+__debug=$0
+_guineapig=$1
+__steptrap_calls=0
+
+shift
+
+shopt -s extglob       # turn on extglob so we can parse the debugger funcs
+
+function _steptrap
+{
+  local i=0
+
+  _curline=$1
+
+  if (( ++__steptrap_calls > 1 && $_curline == 1 )); then
+    return
+  fi
+
+  if [ -n "$_disps" ]; then
+    while (( $i < ${#_disps[@]} ))
+    do
+      if [ -n "${_disps[$i]}" ]; then
+        _msg "${_disps[$i]}: \c"
+        eval _msg ${_disps[$i]}
+      fi
+      let i=$i+1
+    done
+  fi
+
+  if (( $_trace )); then
+    _showline $_curline
+  fi
+
+  if (( $_steps >= 0 )); then
+    let _steps="$_steps - 1"
+  fi
+
+  if _at_linenumbp ; then
+    _msg "Reached breakpoint at line $_curline"
+    _showline $_curline
+    _cmdloop
+  elif [ -n "$_brcond" ] && eval $_brcond; then
+    _msg "Break condition $_brcond true at line $_curline"
+    _showline $_curline
+    _cmdloop
+  elif (( $_steps == 0 )); then
+    # Assuming a real script will have the "#! /bin/sh" at line 1,
+    # assume that when $_curline == 1 we are inside backticks.
+    if (( ! $_trace )); then
+      _msg "Stopped at line $_curline"
+      _showline $_curline
+    fi
+    _cmdloop
+  fi
+}
+
+function _setbp
+{
+  local i f line _x
+
+  if [ -z "$1" ]; then
+    _listbp
+    return
+  fi
+
+  eval "$_seteglob"
+
+  if [[ $1 == *(\+)[1-9]*([0-9]) ]]; then
+    case $1 in
+    +*)
+      # normalize argument, then double it (+2 -> +2 + 2 = 4)
+      _x=${1##*([!1-9])}       # cut off non-numeric prefix
+      _x=${x%%*([!0-9])}       # cut off non-numeric suffix
+      f=$(( $1 + $_x ))
+      ;;
+    *)
+      f=$(( $1 ))
+      ;;
+    esac
+
+    # find the next valid line
+    line="${_lines[$f]}"
+    while _invalidbreakp $f
+    do
+      (( f++ ))
+      line="${_lines[$f]}"
+    done
+
+    if (( $f != $1 ))
+    then
+      _msg "Line $1 is not a valid breakpoint"
+    fi
+
+    if [ -n "${_lines[$f]}" ]; then
+      _linebp[$1]=$1;
+      _msg "Breakpoint set at line $f"
+    else
+      _msg "Breakpoints can only be set on executable lines"
+    fi
+  else
+    _msg "Please specify a numeric line number"
+  fi
+
+  eval "$_resteglob"
+}
+
+function _listbp
+{
+  local i
+  
+  if [ -n "$_linebp" ]; then
+    _msg "Breakpoints:"
+    for i in ${_linebp[*]}; do
+      _showline $i
+    done
+  else
+    _msg "No breakpoints have been set"
+  fi
+}
+
+function _clearbp
+{
+  local i
+
+  if [ -z "$1" ]; then
+    read -e -p "Delete all breakpoints? "
+    case $REPLY in
+    [yY]*)
+      unset _linebp[*]
+      _msg "All breakpoints have been cleared"
+      ;;
+    esac
+    return 0
+  fi
+
+  eval "$_seteglob"
+
+  if [[ $1 == [1-9]*([0-9]) ]]; then
+    unset _linebp[$1]
+    _msg "Breakpoint cleared at line $1"
+  else
+    _msg "Please specify a numeric line number"
+  fi
+
+  eval "$_resteglob"
+}
+
+function _setbc
+{
+  if (( $# > 0 )); then
+    _brcond=$@
+    _msg "Break when true: $_brcond"
+  else
+    _brcond=
+    _msg "Break condition cleared"
+  fi
+}
+
+function _setdisp
+{
+  if [ -z "$1" ]; then
+    _listdisp
+  else
+    _disps[${#_disps[@]}]="$1"
+    if (( ${#_disps[@]} < 10 ))
+    then
+      _msg " ${#_disps[@]}: $1"
+    else
+      _msg "${#_disps[@]}: $1"
+    fi
+  fi
+}
+
+function _listdisp
+{
+  local i=0 j
+  
+  if [ -n "$_disps" ]; then
+    while (( $i < ${#_disps[@]} ))
+    do
+      let j=$i+1
+    if (( ${#_disps[@]} < 10 ))
+    then
+      _msg " $j: ${_disps[$i]}"
+    else
+      _msg "$j: ${_disps[$i]}"
+    fi
+      let i=$j
+    done
+  else
+    _msg "No displays have been set"
+  fi
+}
+
+function _cleardisp
+{
+  if (( $# < 1 )) ; then
+    read -e -p "Delete all display expressions? "
+    case $REPLY in
+    [Yy]*)
+      unset _disps[*]
+      _msg "All breakpoints have been cleared"
+      ;;
+    esac
+    return 0
+  fi
+
+  eval "$_seteglob"
+
+  if [[ $1 == [1-9]*([0-9]) ]]; then
+    unset _disps[$1]
+    _msg "Display $i has been cleared"
+  else
+    _listdisp
+    _msg "Please specify a numeric display number"
+  fi
+
+  eval "$_resteglob"
+}   
+
+# usage _ftrace -u funcname [funcname...]
+function _ftrace
+{
+  local _opt=-t _tmsg="enabled" _func 
+  if [[ $1 == -u ]]; then
+       _opt=+t
+       _tmsg="disabled"
+       shift
+  fi
+  for _func; do
+         declare -f $_opt $_func
+         _msg "Tracing $_tmsg for function $_func"
+  done
+}
+
+function _cmdloop
+{
+  local cmd args
+
+  while read -e -p "bashdb> " cmd args; do
+    test -n "$cmd" && history -s "$cmd $args"  # save on history list
+    test -n "$cmd" || { set $_lastcmd; cmd=$1; shift; args=$*; }
+    if [ -n "$cmd" ]
+    then
+      case $cmd in
+       b|br|bre|brea|break)
+         _setbp $args
+         _lastcmd="break $args"
+         ;;
+       co|con)
+         _msg "ambiguous command: '$cmd', condition, continue?"
+         ;;
+       cond|condi|condit|conditi|conditio|condition)
+         _setbc $args
+         _lastcmd="condition $args"
+         ;;
+       c|cont|conti|contin|continu|continue)
+         _lastcmd="continue"
+         return
+         ;;
+       d)
+         _msg "ambiguous command: '$cmd', delete, display?"
+         ;;
+       de|del|dele|delet|delete)
+         _clearbp $args
+         _lastcmd="delete $args"
+         ;;
+       di|dis|disp|displ|displa|display)
+         _setdisp $args
+         _lastcmd="display $args"
+         ;;
+       f|ft|ftr|ftra|ftrace)
+         _ftrace $args
+         _lastcmd="ftrace $args"
+         ;;
+       \?|h|he|hel|help)
+         _menu
+         _lastcmd="help"
+         ;;
+       l|li|lis|list)
+         _displayscript $args
+         # _lastcmd is set in the _displayscript function
+         ;;
+       p|pr|pri|prin|print)
+         _examine $args
+         _lastcmd="print $args"
+         ;;
+       q|qu|qui|quit)
+         exit
+         ;;
+       s|st|ste|step|n|ne|nex|next)
+         let _steps=${args:-1}
+         _lastcmd="next $args"
+         return
+         ;;
+       t|tr|tra|trac|trace)
+         _xtrace
+         ;;
+       u|un|und|undi|undis|undisp|undispl|undispla|undisplay)
+         _cleardisp $args
+         _lastcmd="undisplay $args"
+         ;;
+       !*)
+         eval ${cmd#!} $args
+         _lastcmd="$cmd $args"
+         ;;
+       *)
+         _msg "Invalid command: '$cmd'"
+         ;;
+      esac
+    fi
+  done
+}
+
+function _at_linenumbp
+{
+  [[ -n ${_linebp[$_curline]} ]]
+}
+
+function _invalidbreakp
+{
+  local line=${_lines[$1]}
+
+  # XXX - should use shell patterns
+  if test -z "$line" \
+      || expr "$line" : '[ \t]*#.*' > /dev/null \
+      || expr "$line" : '[ \t]*;;[ \t]*$' > /dev/null \
+      || expr "$line" : '[ \t]*[^)]*)[ \t]*$' > /dev/null \
+      || expr "$line" : '[ \t]*;;[ \t]*#.**$' > /dev/null \
+      || expr "$line" : '[ \t]*[^)]*)[ \t]*;;[ \t]*$' > /dev/null \
+      || expr "$line" : '[ \t]*[^)]*)[ \t]*;;*[ \t]*#.*$' > /dev/null
+  then
+    return 0
+  fi
+
+  return 1
+}
+
+function _examine
+{
+  if [ -n "$*" ]; then
+    _msg "$args: \c"
+    eval _msg $args
+  else
+    _msg "Nothing to print"
+  fi
+}
+
+function _displayscript
+{
+  local i j start end bp cl
+
+  if (( $# == 1 )); then       # list 5 lines on either side of $1
+    if [ $1 = "%" ]; then
+      let start=1
+      let end=${#_lines[@]}
+    else
+      let start=$1-5
+      let end=$1+5
+    fi
+  elif (( $# > 1 )); then      # list between start and end
+    if [ $1 = "^" ]; then
+      let start=1
+    else
+      let start=$1
+    fi
+
+    if [ $2 = "\$" ]; then
+      let end=${#_lines[@]}
+    else
+      let end=$2
+    fi
+  else                         # list 5 lines on either side of current line
+    let start=$_curline-5
+    let end=$_curline+5
+  fi
+
+  # normalize start and end
+  if (( $start < 1 )); then
+    start=1
+  fi
+  if (( $end > ${#_lines[@]} )); then
+    end=${#_lines[@]}
+  fi
+
+  cl=$(( $end - $start ))
+  if (( $cl > ${LINES-24} )); then
+    pager=${PAGER-more}
+  else
+    pager=cat
+  fi
+  
+  i=$start
+  ( while (( $i <= $end )); do
+      _showline $i
+      let i=$i+1
+    done ) 2>&1 | $pager
+
+  # calculate the next block of lines
+  start=$(( $end + 1 ))
+  end=$(( $start + 11 ))
+  if (( $end > ${#_lines[@]} ))
+  then
+    end=${#_lines[@]}
+  fi
+
+  _lastcmd="list $start $end"
+}
+
+function _xtrace
+{
+  let _trace="! $_trace"
+  if (( $_trace )); then
+    _msg "Execution trace on"
+  else
+    _msg "Execution trace off"
+  fi
+}
+       
+function _msg
+{
+  echo -e "$@" >&2
+}
+
+function _showline
+{
+  local i=0 bp=' ' line=$1 cl=' '
+
+  if [[ -n ${_linebp[$line]} ]]; then
+    bp='*'
+  fi
+
+  if  (( $_curline == $line )); then
+    cl=">"
+  fi
+
+  if (( $line < 100 )); then
+    _msg "$_guineapig:$line   $bp $cl${_lines[$line]}"
+  elif (( $line < 10 )); then
+    _msg "$_guineapig:$line  $bp $cl${_lines[$line]}"
+  elif (( $line > 0 )); then
+    _msg "$_guineapig:$line $bp $cl${_lines[$line]}"
+  fi
+}
+
+function _cleanup
+{
+  rm -f $__debug $_potbelliedpig 2> /dev/null
+}
+
+function _menu
+{
+  _msg 'bashdb commands:
+       break N         set breakpoint at line N
+       break           list breakpoints & break condition
+       condition foo   set break condition to foo
+       condition       clear break condition
+       delete N        clear breakpoint at line N
+       delete          clear all breakpoints
+       display EXP     evaluate and display EXP for each debug step
+       display         show a list of display expressions
+       undisplay N     remove display expression N
+       list N M        display all lines of script between N and M
+       list N          display 5 lines of script either side of line N
+       list            display 5 lines if script either side of current line
+       continue        continue execution upto next breakpoint
+       next [N]        execute [N] statements (default 1)
+       print expr      prints the value of an expression
+       trace           toggle execution trace on/off
+       ftrace [-u] func        make the debugger step into function FUNC
+                       (-u turns off tracing FUNC)
+       help            print this menu
+       ! string        passes string to a shell
+       quit            quit'
+}
+
+shopt -u extglob
+
+HISTFILE=~/.bashdb_history
+set -o history
+set +H
+
+# strings to save and restore the setting of `extglob' in debugger functions
+# that need it
+_seteglob='local __eopt=-u ; shopt -q extglob && __eopt=-s ; shopt -s extglob'
+_resteglob='shopt $__eopt extglob'
+
+_linebp=()
+let _trace=0
+let _i=1
+
+# Be careful about quoted newlines
+_potbelliedpig=${TMPDIR-/tmp}/$_guineapig.$$
+sed 's,\\$,\\\\,' $_guineapig > $_potbelliedpig
+
+_msg "Reading source from file: $_guineapig"
+while read; do
+  _lines[$_i]=$REPLY
+  let _i=$_i+1
+done < $_potbelliedpig
+
+trap _cleanup EXIT
+# Assuming a real script will have the "#! /bin/sh" at line 1,
+# don't stop at line 1 on the first run
+let _steps=1
+LINENO=-1
+trap '_steptrap $LINENO' DEBUG
index 21681aa42a97cc471eaac5ad1ea049c9702ba3ff..b2c785beab9a9d77afa2667c594446289f7c2616 100644 (file)
@@ -115,6 +115,7 @@ malloc.o: $(BUILD_DIR)/config.h $(topdir)/bashtypes.h getpagesize.h
 xmalloc.o: $(BUILD_DIR)/config.h $(BASHINCDIR)/ansi_stdlib.h
 trace.o: ${BUILD_DIR}/config.h
 table.o: ${BUILD_DIR}/config.h
+watch.o: ${BUILD_DIR}/config.h
 
 malloc.o: ${srcdir}/imalloc.h ${srcdir}/mstats.h
 malloc.o: ${srcdir}/table.h ${srcdir}/watch.h
index cb04c982343a590a8efc5efe70472824d7e52eba..def3a111bd32d8f23ee615ed19f967fd98a37744 100644 (file)
@@ -86,6 +86,8 @@
 /* Some systems define these; we want our definitions. */
 #undef ISPRINT
 
+/* Beware:  these only work with single-byte ASCII characters. */
+
 #define ISALNUM(c)     (IN_CTYPE_DOMAIN (c) && isalnum (c))
 #define ISALPHA(c)     (IN_CTYPE_DOMAIN (c) && isalpha (c))
 #define ISDIGIT(c)     (IN_CTYPE_DOMAIN (c) && isdigit (c))
diff --git a/lib/readline/chardefs.h~ b/lib/readline/chardefs.h~
new file mode 100644 (file)
index 0000000..cb04c98
--- /dev/null
@@ -0,0 +1,163 @@
+/* chardefs.h -- Character definitions for readline. */
+
+/* Copyright (C) 1994 Free Software Foundation, Inc.
+
+   This file is part of the GNU Readline Library, a library for
+   reading lines of text with interactive input and history editing.
+
+   The GNU Readline Library 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 2, or
+   (at your option) any later version.
+
+   The GNU Readline Library 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.
+
+   The GNU General Public License is often shipped with GNU software, and
+   is generally kept in a file called COPYING or LICENSE.  If you do not
+   have a copy of the license, write to the Free Software Foundation,
+   59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+#ifndef _CHARDEFS_H_
+#define _CHARDEFS_H_
+
+#include <ctype.h>
+
+#if defined (HAVE_CONFIG_H)
+#  if defined (HAVE_STRING_H)
+#    if ! defined (STDC_HEADERS) && defined (HAVE_MEMORY_H)
+#      include <memory.h>
+#    endif
+#    include <string.h>
+#  endif /* HAVE_STRING_H */
+#  if defined (HAVE_STRINGS_H)
+#    include <strings.h>
+#  endif /* HAVE_STRINGS_H */
+#else
+#  include <string.h>
+#endif /* !HAVE_CONFIG_H */
+
+#ifndef whitespace
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+#endif
+
+#ifdef CTRL
+#  undef CTRL
+#endif
+#ifdef UNCTRL
+#  undef UNCTRL
+#endif
+
+/* Some character stuff. */
+#define control_character_threshold 0x020   /* Smaller than this is control. */
+#define control_character_mask 0x1f        /* 0x20 - 1 */
+#define meta_character_threshold 0x07f     /* Larger than this is Meta. */
+#define control_character_bit 0x40         /* 0x000000, must be off. */
+#define meta_character_bit 0x080           /* x0000000, must be on. */
+#define largest_char 255                   /* Largest character value. */
+
+#define CTRL_CHAR(c) ((c) < control_character_threshold && (((c) & 0x80) == 0))
+#define META_CHAR(c) ((c) > meta_character_threshold && (c) <= largest_char)
+
+#define CTRL(c) ((c) & control_character_mask)
+#define META(c) ((c) | meta_character_bit)
+
+#define UNMETA(c) ((c) & (~meta_character_bit))
+#define UNCTRL(c) _rl_to_upper(((c)|control_character_bit))
+
+#if defined STDC_HEADERS || (!defined (isascii) && !defined (HAVE_ISASCII))
+#  define IN_CTYPE_DOMAIN(c) 1
+#else
+#  define IN_CTYPE_DOMAIN(c) isascii(c)
+#endif
+
+#if !defined (isxdigit) && !defined (HAVE_ISXDIGIT)
+#  define isxdigit(c)   (isdigit((c)) || ((c) >= 'a' && (c) <= 'f') || ((c) >= 'A' && (c) <= 'F'))
+#endif
+
+#if defined (CTYPE_NON_ASCII)
+#  define NON_NEGATIVE(c) 1
+#else
+#  define NON_NEGATIVE(c) ((unsigned char)(c) == (c))
+#endif
+
+/* Some systems define these; we want our definitions. */
+#undef ISPRINT
+
+#define ISALNUM(c)     (IN_CTYPE_DOMAIN (c) && isalnum (c))
+#define ISALPHA(c)     (IN_CTYPE_DOMAIN (c) && isalpha (c))
+#define ISDIGIT(c)     (IN_CTYPE_DOMAIN (c) && isdigit (c))
+#define ISLOWER(c)     (IN_CTYPE_DOMAIN (c) && islower (c))
+#define ISPRINT(c)     (IN_CTYPE_DOMAIN (c) && isprint (c))
+#define ISUPPER(c)     (IN_CTYPE_DOMAIN (c) && isupper (c))
+#define ISXDIGIT(c)    (IN_CTYPE_DOMAIN (c) && isxdigit (c))
+
+#define _rl_lowercase_p(c)     (NON_NEGATIVE(c) && ISLOWER(c))
+#define _rl_uppercase_p(c)     (NON_NEGATIVE(c) && ISUPPER(c))
+#define _rl_digit_p(c)         ((c) >= '0' && (c) <= '9')
+
+#define _rl_pure_alphabetic(c) (NON_NEGATIVE(c) && ISALPHA(c))
+#define ALPHABETIC(c)          (NON_NEGATIVE(c) && ISALNUM(c))
+
+#ifndef _rl_to_upper
+#  define _rl_to_upper(c) (_rl_lowercase_p(c) ? toupper((unsigned char)c) : (c))
+#  define _rl_to_lower(c) (_rl_uppercase_p(c) ? tolower((unsigned char)c) : (c))
+#endif
+
+#ifndef _rl_digit_value
+#  define _rl_digit_value(x) ((x) - '0')
+#endif
+
+#ifndef _rl_isident
+#  define _rl_isident(c) (ISALNUM(c) || (c) == '_')
+#endif
+
+#ifndef ISOCTAL
+#  define ISOCTAL(c)   ((c) >= '0' && (c) <= '7')
+#endif
+#define OCTVALUE(c)    ((c) - '0')
+
+#define HEXVALUE(c) \
+  (((c) >= 'a' && (c) <= 'f') \
+       ? (c)-'a'+10 \
+       : (c) >= 'A' && (c) <= 'F' ? (c)-'A'+10 : (c)-'0')
+
+#ifndef NEWLINE
+#define NEWLINE '\n'
+#endif
+
+#ifndef RETURN
+#define RETURN CTRL('M')
+#endif
+
+#ifndef RUBOUT
+#define RUBOUT 0x7f
+#endif
+
+#ifndef TAB
+#define TAB '\t'
+#endif
+
+#ifdef ABORT_CHAR
+#undef ABORT_CHAR
+#endif
+#define ABORT_CHAR CTRL('G')
+
+#ifdef PAGE
+#undef PAGE
+#endif
+#define PAGE CTRL('L')
+
+#ifdef SPACE
+#undef SPACE
+#endif
+#define SPACE ' '      /* XXX - was 0x20 */
+
+#ifdef ESC
+#undef ESC
+#endif
+#define ESC CTRL('[')
+
+#endif  /* _CHARDEFS_H_ */
index 866551113177180b2ba561a9958ac5798ca5478d..90cd9971e8e92abd8aecaf40fe4ea588af2449ac 100644 (file)
@@ -8,7 +8,7 @@
 .\"
 .\"    Last Change: Tue Sep 13 12:07:26 EDT 2005
 .\"
-.TH READLINE 3 "2005 Sep 13" "GNU Readline 5.1-alpha1"
+.TH READLINE 3 "2005 Sep 13" "GNU Readline 5.1-beta1"
 .\"
 .\" File Name macro.  This used to be `.PN', for Path Name,
 .\" but Sun doesn't seem to like that very much.
index 475348762543c2a3267036c34979229f911f7c8a..10eb76a73be8a8a5be1a30b74d75e7dbab4f1c36 100644 (file)
@@ -2,8 +2,8 @@
 Copyright (C) 1988-2005 Free Software Foundation, Inc. 
 @end ignore
 
-@set EDITION 5.1-alpha1
-@set VERSION 5.1-alpha1
+@set EDITION 5.1-beta1
+@set VERSION 5.1-beta1
 @set UPDATED 13 September 2005
 @set UPDATED-MONTH September 2005
 
index 31aa0a4fe79076baa64fc0ed2e3a4c8cf5a0e40d..ed7ab780a3f299ba5563247e0095a31c0a4efc21 100644 (file)
@@ -323,7 +323,7 @@ _rl_isearch_dispatch (cxt, c)
      _rl_search_cxt *cxt;
      int c;
 {
-  int n, wstart, wlen, limit;
+  int n, wstart, wlen, limit, cval;
   rl_command_func_t *f;
 
   f = (rl_command_func_t *)NULL;
@@ -456,14 +456,20 @@ _rl_isearch_dispatch (cxt, c)
        }
 
       /* if not in a word, move to one. */
-      if (rl_alphabetic(rl_line_buffer[wstart]) == 0)
+      cval = _rl_char_value (rl_line_buffer, wstart);
+      if (_rl_walphabetic (cval) == 0)
        {
          rl_ding ();
          break;
        }
-      n = wstart;
-      while (n < rl_end && rl_alphabetic(rl_line_buffer[n]))
-       n++;
+      n = MB_NEXTCHAR (rl_line_buffer, wstart, 1, MB_FIND_NONZERO);;
+      while (n < rl_end)
+       {
+         cval = _rl_char_value (rl_line_buffer, n);
+         if (_rl_walphabetic (cval) == 0)
+           break;
+         n = MB_NEXTCHAR (rl_line_buffer, n, 1, MB_FIND_NONZERO);;
+       }
       wlen = n - wstart + 1;
       if (cxt->search_string_index + wlen + 1 >= cxt->search_string_size)
        {
index 88b398518340c820148698ec107e98a84c37f29d..e216662d8c557477f7cdecf43f06924eaa0604ac 100644 (file)
@@ -101,6 +101,8 @@ _rl_scxt_alloc (type, flags)
   cxt->last_found_line = cxt->save_line;
   cxt->prev_line_found = 0;
 
+  cxt->save_undo_list = 0;
+
   cxt->history_pos = 0;
   cxt->direction = 0;
 
@@ -321,7 +323,7 @@ _rl_isearch_dispatch (cxt, c)
      _rl_search_cxt *cxt;
      int c;
 {
-  int n, wstart, wlen, limit;
+  int n, wstart, wlen, limit, cval;
   rl_command_func_t *f;
 
   f = (rl_command_func_t *)NULL;
@@ -454,14 +456,20 @@ _rl_isearch_dispatch (cxt, c)
        }
 
       /* if not in a word, move to one. */
-      if (rl_alphabetic(rl_line_buffer[wstart]) == 0)
+      cval = _rl_char_value (rl_line_buffer, wstart);
+      if (rl_walphabetic (cval) == 0)
        {
          rl_ding ();
          break;
        }
-      n = wstart;
-      while (n < rl_end && rl_alphabetic(rl_line_buffer[n]))
-       n++;
+      n = MB_NEXTCHAR (rl_line_buffer, wstart, 1, MB_FIND_NONZERO);;
+      while (n < rl_end)
+       {
+         cval = _rl_char_value (rl_line_buffer, n);
+         if (rl_walphabetic (cval) == 0)
+           break;
+         n = MB_NEXTCHAR (rl_line_buffer, n, 1, MB_FIND_NONZERO);;
+       }
       wlen = n - wstart + 1;
       if (cxt->search_string_index + wlen + 1 >= cxt->search_string_size)
        {
index d3628be825375fe002db72bbd1c3e009bcf0bbb7..11adacb45eadc23f116778042c46d6ba5bfc1030 100644 (file)
@@ -100,6 +100,9 @@ extern int _rl_is_mbchar_matched PARAMS((char *, int, int, char *, int));
 extern wchar_t _rl_char_value PARAMS((char *, int));
 extern int _rl_walphabetic PARAMS((wchar_t));
 
+#define _rl_to_wupper(wc)      (iswlower (wc) ? towupper (wc) : (wc))
+#define _rl_to_wlower(wc)      (iswupper (wc) ? towlower (wc) : (wc))
+
 #define MB_NEXTCHAR(b,s,c,f) \
        ((MB_CUR_MAX > 1 && rl_byte_oriented == 0) \
                ? _rl_find_next_mbchar ((b), (s), (c), (f)) \
@@ -127,6 +130,9 @@ extern int _rl_walphabetic PARAMS((wchar_t));
 
 #define _rl_walphabetic(c)     (rl_alphabetic (c))
 
+#define _rl_to_wupper(c)       (_rl_to_upper (c))
+#define _rl_to_wlower(c)       (_rl_to_lower (c))
+
 #define MB_NEXTCHAR(b,s,c,f)   ((s) + (c))
 #define MB_PREVCHAR(b,s,f)     ((s) - 1)
 
index eb71821623d786c32174774ff236ec23c7abee0a..d3628be825375fe002db72bbd1c3e009bcf0bbb7 100644 (file)
@@ -97,14 +97,14 @@ extern int _rl_read_mbstring PARAMS((int, char *, int));
 
 extern int _rl_is_mbchar_matched PARAMS((char *, int, int, char *, int));
 
-extern int _rl_char_value PARAMS((char *, int));
+extern wchar_t _rl_char_value PARAMS((char *, int));
 extern int _rl_walphabetic PARAMS((wchar_t));
 
-#define NEXT_MBCHAR(b,s,c,f) \
+#define MB_NEXTCHAR(b,s,c,f) \
        ((MB_CUR_MAX > 1 && rl_byte_oriented == 0) \
                ? _rl_find_next_mbchar ((b), (s), (c), (f)) \
                : ((s) + (c)))
-#define PREV_MBCHAR(b,s,f) \
+#define MB_PREVCHAR(b,s,f) \
        ((MB_CUR_MAX > 1 && rl_byte_oriented == 0) \
                ? _rl_find_prev_mbchar ((b), (s), (f)) \
                : ((s) - 1))
@@ -127,8 +127,8 @@ extern int _rl_walphabetic PARAMS((wchar_t));
 
 #define _rl_walphabetic(c)     (rl_alphabetic (c))
 
-#define NEXT_MBCHAR(b,s,c,f)   ((s) + (c))
-#define PREV_MBCHAR(b,s,f)     ((s) - 1)
+#define MB_NEXTCHAR(b,s,c,f)   ((s) + (c))
+#define MB_PREVCHAR(b,s,f)     ((s) - 1)
 
 #define MB_INVALIDCH(x)                (0)
 #define MB_NULLWCH(x)          (0)
index 68cfa0665ad35b1880ad0bc3dcba28d81d6ebe3c..9053e96903bb1c7bb78323a42c80a35061042a14 100644 (file)
@@ -1293,8 +1293,7 @@ rl_change_case (count, op)
       else
        {
          mbrtowc (&wc, rl_line_buffer + start, end - start, &ps);
-         nwc = (nop == UpCase) ? (iswlower (wc) ? towupper (wc) : wc)
-                               : (iswupper (wc) ? towlower (wc) : wc);
+         nwc = (nop == UpCase) ? _rl_to_wupper (wc) : _rl_to_wlower (wc);
          if  (nwc != wc)       /*  just skip unchanged characters */
            {
              mblen = wcrtomb (mb, nwc, &ps);
index 90b0c8da753ccfeeb1bdab0b871e7cf27cdc8655..68cfa0665ad35b1880ad0bc3dcba28d81d6ebe3c 100644 (file)
@@ -1,6 +1,6 @@
 /* text.c -- text handling commands for readline. */
 
-/* Copyright (C) 1987-2004 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2005 Free Software Foundation, Inc.
 
    This file is part of the GNU Readline Library, a library for
    reading lines of text with interactive input and history editing.
 static int rl_change_case PARAMS((int, int));
 static int _rl_char_search PARAMS((int, int, int));
 
+#if defined (READLINE_CALLBACKS)
+static int _rl_insert_next_callback PARAMS((_rl_callback_generic_arg *));
+static int _rl_char_search_callback PARAMS((_rl_callback_generic_arg *));
+#endif
+
 /* **************************************************************** */
 /*                                                                 */
 /*                     Insert and Delete                           */
@@ -767,10 +772,8 @@ _rl_insert_char (count, c)
       return 0;
     }
 
-#if defined (HANDLE_MULTIBYTE)
   if (MB_CUR_MAX == 1 || rl_byte_oriented)
     {
-#endif
       /* We are inserting a single character.
         If there is pending input, then make a string of all of the
         pending characters that are bound to rl_insert, and insert
@@ -786,8 +789,8 @@ _rl_insert_char (count, c)
          str[0] = c;
          rl_insert_text (str);
        }
-#if defined (HANDLE_MULTIBYTE)
     }
+#if defined (HANDLE_MULTIBYTE)
   else
     {
       rl_insert_text (incoming);
@@ -844,27 +847,63 @@ rl_insert (count, c)
 }
 
 /* Insert the next typed character verbatim. */
-int
-rl_quoted_insert (count, key)
-     int count, key;
+static int
+_rl_insert_next (count)
+     int count;
 {
   int c;
 
-#if defined (HANDLE_SIGNALS)
-  _rl_disable_tty_signals ();
-#endif
-
   RL_SETSTATE(RL_STATE_MOREINPUT);
   c = rl_read_key ();
   RL_UNSETSTATE(RL_STATE_MOREINPUT);
 
 #if defined (HANDLE_SIGNALS)
-  _rl_restore_tty_signals ();
+  if (RL_ISSTATE (RL_STATE_CALLBACK) == 0)
+    _rl_restore_tty_signals ();
 #endif
 
   return (_rl_insert_char (count, c));  
 }
 
+#if defined (READLINE_CALLBACKS)
+static int
+_rl_insert_next_callback (data)
+     _rl_callback_generic_arg *data;
+{
+  int count;
+
+  count = data->count;
+
+  /* Deregister function, let rl_callback_read_char deallocate data */
+  _rl_callback_func = 0;
+  _rl_want_redisplay = 1;
+  return _rl_insert_next (count);
+}
+#endif
+  
+int
+rl_quoted_insert (count, key)
+     int count, key;
+{
+  /* Let's see...should the callback interface futz with signal handling? */
+#if defined (HANDLE_SIGNALS)
+  if (RL_ISSTATE (RL_STATE_CALLBACK) == 0)
+    _rl_disable_tty_signals ();
+#endif
+
+#if defined (READLINE_CALLBACKS)
+  if (RL_ISSTATE (RL_STATE_CALLBACK))
+    {
+      _rl_callback_data = _rl_callback_data_alloc (count);
+      _rl_callback_func = _rl_insert_next_callback;
+      return (0);
+    }
+#endif
+      
+  return _rl_insert_next (count);
+}
+
 /* Insert a tab character. */
 int
 rl_tab_insert (count, key)
@@ -1046,11 +1085,9 @@ rl_delete (count, key)
   if (count > 1 || rl_explicit_arg)
     {
       int orig_point = rl_point;
-#if defined (HANDLE_MULTIBYTE)
       if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
        rl_forward_char (count, key);
       else
-#endif
        rl_forward_byte (count, key);
 
       r = rl_kill_text (orig_point, rl_point);
@@ -1060,11 +1097,8 @@ rl_delete (count, key)
   else
     {
       int new_point;
-      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
-       new_point = _rl_find_next_mbchar (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO);
-      else
-       new_point = rl_point + 1;
-       
+
+      new_point = MB_NEXTCHAR (rl_line_buffer, rl_point, 1, MB_FIND_NONZERO);
       return (rl_delete_text (rl_point, new_point));
     }
 }
@@ -1200,42 +1234,81 @@ static int
 rl_change_case (count, op)
      int count, op;
 {
-  register int start, end;
-  int inword, c;
+  int start, next, end;
+  int inword, c, nc, nop;
+#if defined (HANDLE_MULTIBYTE)
+  wchar_t wc, nwc;
+  char mb[MB_LEN_MAX+1];
+  int mblen, p;
+  mbstate_t ps;
+#endif
 
   start = rl_point;
   rl_forward_word (count, 0);
   end = rl_point;
 
+  if (op != UpCase && op != DownCase && op != CapCase)
+    {
+      rl_ding ();
+      return -1;
+    }
+
   if (count < 0)
     SWAP (start, end);
 
+#if defined (HANDLE_MULTIBYTE)
+  memset (&ps, 0, sizeof (mbstate_t));
+#endif
+
   /* We are going to modify some text, so let's prepare to undo it. */
   rl_modifying (start, end);
 
-  for (inword = 0; start < end; start++)
+  inword = 0;
+  while (start < end)
     {
-      c = rl_line_buffer[start];
-      switch (op)
-       {
-       case UpCase:
-         rl_line_buffer[start] = _rl_to_upper (c);
-         break;
-
-       case DownCase:
-         rl_line_buffer[start] = _rl_to_lower (c);
-         break;
+      c = _rl_char_value (rl_line_buffer, start);
+      /*  This assumes that the upper and lower case versions are the same width. */
+      next = MB_NEXTCHAR (rl_line_buffer, start, 1, MB_FIND_NONZERO);
 
-       case CapCase:
-         rl_line_buffer[start] = (inword == 0) ? _rl_to_upper (c) : _rl_to_lower (c);
-         inword = rl_alphabetic (rl_line_buffer[start]);
-         break;
+      if (_rl_walphabetic (c) == 0)
+       {
+         inword = 0;
+         start = next;
+         continue;
+       }
 
-       default:
-         rl_ding ();
-         return -1;
+      if (op == CapCase)
+       {
+         nop = inword ? DownCase : UpCase;
+         inword = 1;
        }
+      else
+       nop = op;
+      if (MB_CUR_MAX == 1 || rl_byte_oriented || isascii (c))
+       {
+         nc = (nop == UpCase) ? _rl_to_upper (c) : _rl_to_lower (c);
+         rl_line_buffer[start] = nc;
+       }
+#if defined (HANDLE_MULTIBYTE)
+      else
+       {
+         mbrtowc (&wc, rl_line_buffer + start, end - start, &ps);
+         nwc = (nop == UpCase) ? (iswlower (wc) ? towupper (wc) : wc)
+                               : (iswupper (wc) ? towlower (wc) : wc);
+         if  (nwc != wc)       /*  just skip unchanged characters */
+           {
+             mblen = wcrtomb (mb, nwc, &ps);
+             if (mblen > 0)
+               mb[mblen] = '\0';
+             /* Assume the same width */
+             strncpy (rl_line_buffer + start, mb, mblen);
+           }
+       }
+#endif
+
+      start = next;
     }
+
   rl_point = end;
   return 0;
 }
@@ -1315,11 +1388,11 @@ rl_transpose_chars (count, key)
 {
 #if defined (HANDLE_MULTIBYTE)
   char *dummy;
-  int i, prev_point;
+  int i;
 #else
   char dummy[2];
 #endif
-  int char_length;
+  int char_length, prev_point;
 
   if (count == 0)
     return 0;
@@ -1334,20 +1407,12 @@ rl_transpose_chars (count, key)
 
   if (rl_point == rl_end)
     {
-      if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
-       rl_point = _rl_find_prev_mbchar (rl_line_buffer, rl_point, MB_FIND_NONZERO);
-      else
-       --rl_point;
+      rl_point = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO);
       count = 1;
     }
 
-#if defined (HANDLE_MULTIBYTE)
   prev_point = rl_point;
-  if (MB_CUR_MAX > 1 && rl_byte_oriented == 0)
-    rl_point = _rl_find_prev_mbchar (rl_line_buffer, rl_point, MB_FIND_NONZERO);
-  else
-#endif
-    rl_point--;
+  rl_point = MB_PREVCHAR (rl_line_buffer, rl_point, MB_FIND_NONZERO);
 
 #if defined (HANDLE_MULTIBYTE)
   char_length = prev_point - rl_point;
@@ -1481,10 +1546,33 @@ _rl_char_search (count, fdir, bdir)
 }
 #endif /* !HANDLE_MULTIBYTE */
 
+#if defined (READLINE_CALLBACKS)
+static int
+_rl_char_search_callback (data)
+     _rl_callback_generic_arg *data;
+{
+  _rl_callback_func = 0;
+  _rl_want_redisplay = 1;
+
+  return (_rl_char_search (data->count, data->i1, data->i2));
+}
+#endif
+
 int
 rl_char_search (count, key)
      int count, key;
 {
+#if defined (READLINE_CALLBACKS)
+  if (RL_ISSTATE (RL_STATE_CALLBACK))
+    {
+      _rl_callback_data = _rl_callback_data_alloc (count);
+      _rl_callback_data->i1 = FFIND;
+      _rl_callback_data->i2 = BFIND;
+      _rl_callback_func = _rl_char_search_callback;
+      return (0);
+    }
+#endif
+  
   return (_rl_char_search (count, FFIND, BFIND));
 }
 
@@ -1492,6 +1580,17 @@ int
 rl_backward_char_search (count, key)
      int count, key;
 {
+#if defined (READLINE_CALLBACKS)
+  if (RL_ISSTATE (RL_STATE_CALLBACK))
+    {
+      _rl_callback_data = _rl_callback_data_alloc (count);
+      _rl_callback_data->i1 = BFIND;
+      _rl_callback_data->i2 = FFIND;
+      _rl_callback_func = _rl_char_search_callback;
+      return (0);
+    }
+#endif
+
   return (_rl_char_search (count, BFIND, FFIND));
 }
 
diff --git a/parse.y b/parse.y
index 5fa6b75eb93932e595efe57f464a1fad04dc4922..eb4ef7f85477d17e3f9e41a3ce504a6e46932efc 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -2709,6 +2709,7 @@ read_token (command)
 #define P_FIRSTCLOSE   0x01
 #define P_ALLOWESC     0x02
 #define P_DQUOTE       0x04
+#define P_COMMAND      0x08    /* parsing a command, so look for comments */
 
 static char matched_pair_error;
 static char *
@@ -2717,13 +2718,14 @@ parse_matched_pair (qc, open, close, lenp, flags)
      int open, close;
      int *lenp, flags;
 {
-  int count, ch, was_dollar;
+  int count, ch, was_dollar, in_comment, check_comment;
   int pass_next_character, nestlen, ttranslen, start_lineno;
   char *ret, *nestret, *ttrans;
   int retind, retsize, rflags;
 
   count = 1;
-  pass_next_character = was_dollar = 0;
+  pass_next_character = was_dollar = in_comment = 0;
+  check_comment = (flags & P_COMMAND) && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0;
 
   /* RFLAGS is the set of flags we want to pass to recursive calls. */
   rflags = (qc == '"') ? P_DQUOTE : (flags & P_DQUOTE);
@@ -2751,6 +2753,21 @@ parse_matched_pair (qc, open, close, lenp, flags)
       if (ch == '\n' && SHOULD_PROMPT ())
        prompt_again ();
 
+      if (in_comment)
+       {
+         /* Add this character. */
+         RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+         ret[retind++] = ch;
+
+         if (ch == '\n')
+           in_comment = 0;
+
+         continue;
+       }
+      /* Not exactly right yet */
+      else if (check_comment && in_comment == 0 && ch == '#' && (retind == 0 || ret[retind-1] == '\n' || whitespace (ret[retind -1])))
+       in_comment = 1;
+
       if (pass_next_character)         /* last char was backslash */
        {
          pass_next_character = 0;
@@ -2873,7 +2890,7 @@ add_nestret:
            }
          FREE (nestret);
        }
-      else if MBTEST(qc == '`' && (ch == '"' || ch == '\''))
+      else if MBTEST(qc == '`' && (ch == '"' || ch == '\'') && in_comment == 0)
        {
          nestret = parse_matched_pair (0, ch, ch, &nestlen, rflags);
          goto add_nestret;
@@ -3363,7 +3380,7 @@ read_token_word (character)
       if MBTEST(shellquote (character))
        {
          push_delimiter (dstack, character);
-         ttok = parse_matched_pair (character, character, character, &ttoklen, 0);
+         ttok = parse_matched_pair (character, character, character, &ttoklen, (character == '`') ? P_COMMAND : 0);
          pop_delimiter (dstack);
          if (ttok == &matched_pair_error)
            return -1;          /* Bail immediately. */
@@ -3426,7 +3443,7 @@ read_token_word (character)
                     history literally rather than causing a possibly-
                     incorrect `;' to be added. ) */
                  push_delimiter (dstack, peek_char);
-                 ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+                 ttok = parse_matched_pair (cd, '(', ')', &ttoklen, P_COMMAND);
                  pop_delimiter (dstack);
                }
              else
diff --git a/parse.y.save1 b/parse.y.save1
new file mode 100644 (file)
index 0000000..ea9129f
--- /dev/null
@@ -0,0 +1,4911 @@
+/* Yacc grammar for bash. */
+
+/* Copyright (C) 1989-2005 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 2, 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; see the file LICENSE.  If not, write to the Free Software
+   Foundation, 59 Temple Place, Suite 330, Boston, MA 02111 USA. */
+
+%{
+#include "config.h"
+
+#include "bashtypes.h"
+#include "bashansi.h"
+
+#include "filecntl.h"
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#if defined (HAVE_LOCALE_H)
+#  include <locale.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#include <signal.h>
+
+#include "memalloc.h"
+
+#include "bashintl.h"
+
+#define NEED_STRFTIME_DECL     /* used in externs.h */
+
+#include "shell.h"
+#include "trap.h"
+#include "flags.h"
+#include "parser.h"
+#include "mailcheck.h"
+#include "test.h"
+#include "builtins.h"
+#include "builtins/common.h"
+#include "builtins/builtext.h"
+
+#include "shmbutil.h"
+
+#if defined (READLINE)
+#  include "bashline.h"
+#  include <readline/readline.h>
+#endif /* READLINE */
+
+#if defined (HISTORY)
+#  include "bashhist.h"
+#  include <readline/history.h>
+#endif /* HISTORY */
+
+#if defined (JOB_CONTROL)
+#  include "jobs.h"
+#endif /* JOB_CONTROL */
+
+#if defined (ALIAS)
+#  include "alias.h"
+#else
+typedef void *alias_t;
+#endif /* ALIAS */
+
+#if defined (PROMPT_STRING_DECODE)
+#  ifndef _MINIX
+#    include <sys/param.h>
+#  endif
+#  include <time.h>
+#  if defined (TM_IN_SYS_TIME)
+#    include <sys/types.h>
+#    include <sys/time.h>
+#  endif /* TM_IN_SYS_TIME */
+#  include "maxpath.h"
+#endif /* PROMPT_STRING_DECODE */
+
+#define RE_READ_TOKEN  -99
+#define NO_EXPANSION   -100
+
+#ifdef DEBUG
+#  define YYDEBUG 1
+#else
+#  define YYDEBUG 0
+#endif
+
+#if defined (HANDLE_MULTIBYTE)
+#  define last_shell_getc_is_singlebyte \
+       ((shell_input_line_index > 1) \
+               ? shell_input_line_property[shell_input_line_index - 1] \
+               : 1)
+#  define MBTEST(x)    ((x) && last_shell_getc_is_singlebyte)
+#else
+#  define last_shell_getc_is_singlebyte        1
+#  define MBTEST(x)    ((x))
+#endif
+
+#if defined (EXTENDED_GLOB)
+extern int extended_glob;
+#endif
+
+extern int eof_encountered;
+extern int no_line_editing, running_under_emacs;
+extern int current_command_number;
+extern int sourcelevel;
+extern int posixly_correct;
+extern int last_command_exit_value;
+extern int interrupt_immediately;
+extern char *shell_name, *current_host_name;
+extern char *dist_version;
+extern int patch_level;
+extern int dump_translatable_strings, dump_po_strings;
+extern sh_builtin_func_t *last_shell_builtin, *this_shell_builtin;
+#if defined (BUFFERED_INPUT)
+extern int bash_input_fd_changed;
+#endif
+
+extern int errno;
+/* **************************************************************** */
+/*                                                                 */
+/*                 "Forward" declarations                          */
+/*                                                                 */
+/* **************************************************************** */
+
+#ifdef DEBUG
+static void debug_parser __P((int));
+#endif
+
+static int yy_getc __P((void));
+static int yy_ungetc __P((int));
+
+#if defined (READLINE)
+static int yy_readline_get __P((void));
+static int yy_readline_unget __P((int));
+#endif
+
+static int yy_string_get __P((void));
+static int yy_string_unget __P((int));
+static int yy_stream_get __P((void));
+static int yy_stream_unget __P((int));
+
+static int shell_getc __P((int));
+static void shell_ungetc __P((int));
+static void discard_until __P((int));
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+static void push_string __P((char *, int, alias_t *));
+static void pop_string __P((void));
+static void free_string_list __P((void));
+#endif
+
+static char *read_a_line __P((int));
+
+static int reserved_word_acceptable __P((int));
+static int yylex __P((void));
+static int alias_expand_token __P((char *));
+static int time_command_acceptable __P((void));
+static int special_case_tokens __P((char *));
+static int read_token __P((int));
+static char *parse_matched_pair __P((int, int, int, int *, int));
+#if defined (ARRAY_VARS)
+static char *parse_compound_assignment __P((int *));
+#endif
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+static int parse_dparen __P((int));
+static int parse_arith_cmd __P((char **, int));
+#endif
+#if defined (COND_COMMAND)
+static void cond_error __P((void));
+static COND_COM *cond_expr __P((void));
+static COND_COM *cond_or __P((void));
+static COND_COM *cond_and __P((void));
+static COND_COM *cond_term __P((void));
+static int cond_skip_newlines __P((void));
+static COMMAND *parse_cond_command __P((void));
+#endif
+#if defined (ARRAY_VARS)
+static int token_is_assignment __P((char *, int));
+static int token_is_ident __P((char *, int));
+#endif
+static int read_token_word __P((int));
+static void discard_parser_constructs __P((int));
+
+static char *error_token_from_token __P((int));
+static char *error_token_from_text __P((void));
+static void print_offending_line __P((void));
+static void report_syntax_error __P((char *));
+
+static void handle_eof_input_unit __P((void));
+static void prompt_again __P((void));
+#if 0
+static void reset_readline_prompt __P((void));
+#endif
+static void print_prompt __P((void));
+
+#if defined (HISTORY)
+char *history_delimiting_chars __P((void));
+#endif
+
+#if defined (HANDLE_MULTIBYTE)
+static void set_line_mbstate __P((void));
+static char *shell_input_line_property = NULL;
+#else
+#  define set_line_mbstate()
+#endif
+
+extern int yyerror __P((const char *));
+
+#ifdef DEBUG
+extern int yydebug;
+#endif
+
+/* Default prompt strings */
+char *primary_prompt = PPROMPT;
+char *secondary_prompt = SPROMPT;
+
+/* PROMPT_STRING_POINTER points to one of these, never to an actual string. */
+char *ps1_prompt, *ps2_prompt;
+
+/* Handle on the current prompt string.  Indirectly points through
+   ps1_ or ps2_prompt. */
+char **prompt_string_pointer = (char **)NULL;
+char *current_prompt_string;
+
+/* Non-zero means we expand aliases in commands. */
+int expand_aliases = 0;
+
+/* If non-zero, the decoded prompt string undergoes parameter and
+   variable substitution, command substitution, arithmetic substitution,
+   string expansion, process substitution, and quote removal in
+   decode_prompt_string. */
+int promptvars = 1;
+
+/* If non-zero, $'...' and $"..." are expanded when they appear within
+   a ${...} expansion, even when the expansion appears within double
+   quotes. */
+int extended_quote = 1;
+
+/* The decoded prompt string.  Used if READLINE is not defined or if
+   editing is turned off.  Analogous to current_readline_prompt. */
+static char *current_decoded_prompt;
+
+/* The number of lines read from input while creating the current command. */
+int current_command_line_count;
+
+/* Variables to manage the task of reading here documents, because we need to
+   defer the reading until after a complete command has been collected. */
+static REDIRECT *redir_stack[10];
+int need_here_doc;
+
+/* Where shell input comes from.  History expansion is performed on each
+   line when the shell is interactive. */
+static char *shell_input_line = (char *)NULL;
+static int shell_input_line_index;
+static int shell_input_line_size;      /* Amount allocated for shell_input_line. */
+static int shell_input_line_len;       /* strlen (shell_input_line) */
+
+/* Either zero or EOF. */
+static int shell_input_line_terminator;
+
+/* The line number in a script on which a function definition starts. */
+static int function_dstart;
+
+/* The line number in a script on which a function body starts. */
+static int function_bstart;
+
+/* The line number in a script at which an arithmetic for command starts. */
+static int arith_for_lineno;
+
+/* The line number in a script where the word in a `case WORD', `select WORD'
+   or `for WORD' begins.  This is a nested command maximum, since the array
+   index is decremented after a case, select, or for command is parsed. */
+#define MAX_CASE_NEST  128
+static int word_lineno[MAX_CASE_NEST];
+static int word_top = -1;
+
+/* If non-zero, it is the token that we want read_token to return
+   regardless of what text is (or isn't) present to be read.  This
+   is reset by read_token.  If token_to_read == WORD or
+   ASSIGNMENT_WORD, yylval.word should be set to word_desc_to_read. */
+static int token_to_read;
+static WORD_DESC *word_desc_to_read;
+
+static REDIRECTEE redir;
+%}
+
+%union {
+  WORD_DESC *word;             /* the word that we read. */
+  int number;                  /* the number that we read. */
+  WORD_LIST *word_list;
+  COMMAND *command;
+  REDIRECT *redirect;
+  ELEMENT element;
+  PATTERN_LIST *pattern;
+}
+
+/* Reserved words.  Members of the first group are only recognized
+   in the case that they are preceded by a list_terminator.  Members
+   of the second group are for [[...]] commands.  Members of the
+   third group are recognized only under special circumstances. */
+%token IF THEN ELSE ELIF FI CASE ESAC FOR SELECT WHILE UNTIL DO DONE FUNCTION
+%token COND_START COND_END COND_ERROR
+%token IN BANG TIME TIMEOPT
+
+/* More general tokens. yylex () knows how to make these. */
+%token <word> WORD ASSIGNMENT_WORD
+%token <number> NUMBER
+%token <word_list> ARITH_CMD ARITH_FOR_EXPRS
+%token <command> COND_CMD
+%token AND_AND OR_OR GREATER_GREATER LESS_LESS LESS_AND LESS_LESS_LESS
+%token GREATER_AND SEMI_SEMI LESS_LESS_MINUS AND_GREATER LESS_GREATER
+%token GREATER_BAR
+
+/* The types that the various syntactical units return. */
+
+%type <command> inputunit command pipeline pipeline_command
+%type <command> list list0 list1 compound_list simple_list simple_list1
+%type <command> simple_command shell_command
+%type <command> for_command select_command case_command group_command
+%type <command> arith_command
+%type <command> cond_command
+%type <command> arith_for_command
+%type <command> function_def function_body if_command elif_clause subshell
+%type <redirect> redirection redirection_list
+%type <element> simple_command_element
+%type <word_list> word_list pattern
+%type <pattern> pattern_list case_clause_sequence case_clause
+%type <number> timespec
+%type <number> list_terminator
+
+%start inputunit
+
+%left '&' ';' '\n' yacc_EOF
+%left AND_AND OR_OR
+%right '|'
+%%
+
+inputunit:     simple_list simple_list_terminator
+                       {
+                         /* Case of regular command.  Discard the error
+                            safety net,and return the command just parsed. */
+                         global_command = $1;
+                         eof_encountered = 0;
+                         /* discard_parser_constructs (0); */
+                         YYACCEPT;
+                       }
+       |       '\n'
+                       {
+                         /* Case of regular command, but not a very
+                            interesting one.  Return a NULL command. */
+                         global_command = (COMMAND *)NULL;
+                         YYACCEPT;
+                       }
+       |       error '\n'
+                       {
+                         /* Error during parsing.  Return NULL command. */
+                         global_command = (COMMAND *)NULL;
+                         eof_encountered = 0;
+                         /* discard_parser_constructs (1); */
+                         if (interactive)
+                           {
+                             YYACCEPT;
+                           }
+                         else
+                           {
+                             YYABORT;
+                           }
+                       }
+       |       yacc_EOF
+                       {
+                         /* Case of EOF seen by itself.  Do ignoreeof or
+                            not. */
+                         global_command = (COMMAND *)NULL;
+                         handle_eof_input_unit ();
+                         YYACCEPT;
+                       }
+       ;
+
+word_list:     WORD
+                       { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+       |       word_list WORD
+                       { $$ = make_word_list ($2, $1); }
+       ;
+
+redirection:   '>' WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_output_direction, redir);
+                       }
+       |       '<' WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_input_direction, redir);
+                       }
+       |       NUMBER '>' WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_output_direction, redir);
+                       }
+       |       NUMBER '<' WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_input_direction, redir);
+                       }
+       |       GREATER_GREATER WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_appending_to, redir);
+                       }
+       |       NUMBER GREATER_GREATER WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_appending_to, redir);
+                       }
+       |       LESS_LESS WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_reading_until, redir);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       NUMBER LESS_LESS WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_reading_until, redir);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       LESS_LESS_LESS WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_reading_string, redir);
+                       }
+       |       NUMBER LESS_LESS_LESS WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_reading_string, redir);
+                       }
+       |       LESS_AND NUMBER
+                       {
+                         redir.dest = $2;
+                         $$ = make_redirection (0, r_duplicating_input, redir);
+                       }
+       |       NUMBER LESS_AND NUMBER
+                       {
+                         redir.dest = $3;
+                         $$ = make_redirection ($1, r_duplicating_input, redir);
+                       }
+       |       GREATER_AND NUMBER
+                       {
+                         redir.dest = $2;
+                         $$ = make_redirection (1, r_duplicating_output, redir);
+                       }
+       |       NUMBER GREATER_AND NUMBER
+                       {
+                         redir.dest = $3;
+                         $$ = make_redirection ($1, r_duplicating_output, redir);
+                       }
+       |       LESS_AND WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_duplicating_input_word, redir);
+                       }
+       |       NUMBER LESS_AND WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_duplicating_input_word, redir);
+                       }
+       |       GREATER_AND WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_duplicating_output_word, redir);
+                       }
+       |       NUMBER GREATER_AND WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_duplicating_output_word, redir);
+                       }
+       |       LESS_LESS_MINUS WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection
+                           (0, r_deblank_reading_until, redir);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       NUMBER LESS_LESS_MINUS WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection
+                           ($1, r_deblank_reading_until, redir);
+                         redir_stack[need_here_doc++] = $$;
+                       }
+       |       GREATER_AND '-'
+                       {
+                         redir.dest = 0;
+                         $$ = make_redirection (1, r_close_this, redir);
+                       }
+       |       NUMBER GREATER_AND '-'
+                       {
+                         redir.dest = 0;
+                         $$ = make_redirection ($1, r_close_this, redir);
+                       }
+       |       LESS_AND '-'
+                       {
+                         redir.dest = 0;
+                         $$ = make_redirection (0, r_close_this, redir);
+                       }
+       |       NUMBER LESS_AND '-'
+                       {
+                         redir.dest = 0;
+                         $$ = make_redirection ($1, r_close_this, redir);
+                       }
+       |       AND_GREATER WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_err_and_out, redir);
+                       }
+       |       NUMBER LESS_GREATER WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_input_output, redir);
+                       }
+       |       LESS_GREATER WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (0, r_input_output, redir);
+                       }
+       |       GREATER_BAR WORD
+                       {
+                         redir.filename = $2;
+                         $$ = make_redirection (1, r_output_force, redir);
+                       }
+       |       NUMBER GREATER_BAR WORD
+                       {
+                         redir.filename = $3;
+                         $$ = make_redirection ($1, r_output_force, redir);
+                       }
+       ;
+
+simple_command_element: WORD
+                       { $$.word = $1; $$.redirect = 0; }
+       |       ASSIGNMENT_WORD
+                       { $$.word = $1; $$.redirect = 0; }
+       |       redirection
+                       { $$.redirect = $1; $$.word = 0; }
+       ;
+
+redirection_list: redirection
+                       {
+                         $$ = $1;
+                       }
+       |       redirection_list redirection
+                       {
+                         register REDIRECT *t;
+
+                         for (t = $1; t->next; t = t->next)
+                           ;
+                         t->next = $2;
+                         $$ = $1;
+                       }
+       ;
+
+simple_command:        simple_command_element
+                       { $$ = make_simple_command ($1, (COMMAND *)NULL); }
+       |       simple_command simple_command_element
+                       { $$ = make_simple_command ($2, $1); }
+       ;
+
+command:       simple_command
+                       { $$ = clean_simple_command ($1); }
+       |       shell_command
+                       { $$ = $1; }
+       |       shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $1;
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $2;
+                           }
+                         else
+                           tc->redirects = $2;
+                         $$ = $1;
+                       }
+       |       function_def
+                       { $$ = $1; }
+       ;
+
+shell_command: for_command
+                       { $$ = $1; }
+       |       case_command
+                       { $$ = $1; }
+       |       WHILE compound_list DO compound_list DONE
+                       { $$ = make_while_command ($2, $4); }
+       |       UNTIL compound_list DO compound_list DONE
+                       { $$ = make_until_command ($2, $4); }
+       |       select_command
+                       { $$ = $1; }
+       |       if_command
+                       { $$ = $1; }
+       |       subshell
+                       { $$ = $1; }
+       |       group_command
+                       { $$ = $1; }
+       |       arith_command
+                       { $$ = $1; }
+       |       cond_command
+                       { $$ = $1; }
+       |       arith_for_command
+                       { $$ = $1; }
+       ;
+
+for_command:   FOR WORD newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD ';' newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD ';' newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN word_list list_terminator newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN word_list list_terminator newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN list_terminator newline_list DO compound_list DONE
+                       {
+                         $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       FOR WORD newline_list IN list_terminator newline_list '{' compound_list '}'
+                       {
+                         $$ = make_for_command ($2, (WORD_LIST *)NULL, $8, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+arith_for_command:     FOR ARITH_FOR_EXPRS list_terminator newline_list DO compound_list DONE
+                               {
+                                 $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS list_terminator newline_list '{' compound_list '}'
+                               {
+                                 $$ = make_arith_for_command ($2, $6, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS DO compound_list DONE
+                               {
+                                 $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       |               FOR ARITH_FOR_EXPRS '{' compound_list '}'
+                               {
+                                 $$ = make_arith_for_command ($2, $4, arith_for_lineno);
+                                 if (word_top > 0) word_top--;
+                               }
+       ;
+
+select_command:        SELECT WORD newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD ';' newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD ';' newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), $6, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list DO list DONE
+                       {
+                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       SELECT WORD newline_list IN word_list list_terminator newline_list '{' list '}'
+                       {
+                         $$ = make_select_command ($2, REVERSE_LIST ($5, WORD_LIST *), $9, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+case_command:  CASE WORD newline_list IN newline_list ESAC
+                       {
+                         $$ = make_case_command ($2, (PATTERN_LIST *)NULL, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       CASE WORD newline_list IN case_clause_sequence newline_list ESAC
+                       {
+                         $$ = make_case_command ($2, $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       |       CASE WORD newline_list IN case_clause ESAC
+                       {
+                         $$ = make_case_command ($2, $5, word_lineno[word_top]);
+                         if (word_top > 0) word_top--;
+                       }
+       ;
+
+function_def:  WORD '(' ')' newline_list function_body
+                       { $$ = make_function_def ($1, $5, function_dstart, function_bstart); }
+
+       |       FUNCTION WORD '(' ')' newline_list function_body
+                       { $$ = make_function_def ($2, $6, function_dstart, function_bstart); }
+
+       |       FUNCTION WORD newline_list function_body
+                       { $$ = make_function_def ($2, $4, function_dstart, function_bstart); }
+       ;
+
+
+function_body: shell_command
+                       { $$ = $1; }
+       |       shell_command redirection_list
+                       {
+                         COMMAND *tc;
+
+                         tc = $1;
+                         /* According to Posix.2 3.9.5, redirections
+                            specified after the body of a function should
+                            be attached to the function and performed when
+                            the function is executed, not as part of the
+                            function definition command. */
+                         /* XXX - I don't think it matters, but we might
+                            want to change this in the future to avoid
+                            problems differentiating between a function
+                            definition with a redirection and a function
+                            definition containing a single command with a
+                            redirection.  The two are semantically equivalent,
+                            though -- the only difference is in how the
+                            command printing code displays the redirections. */
+                         if (tc->redirects)
+                           {
+                             register REDIRECT *t;
+                             for (t = tc->redirects; t->next; t = t->next)
+                               ;
+                             t->next = $2;
+                           }
+                         else
+                           tc->redirects = $2;
+                         $$ = $1;
+                       }
+       ;
+
+subshell:      '(' compound_list ')'
+                       {
+                         $$ = make_subshell_command ($2);
+                         $$->flags |= CMD_WANT_SUBSHELL;
+                       }
+       ;
+
+if_command:    IF compound_list THEN compound_list FI
+                       { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+       |       IF compound_list THEN compound_list ELSE compound_list FI
+                       { $$ = make_if_command ($2, $4, $6); }
+       |       IF compound_list THEN compound_list elif_clause FI
+                       { $$ = make_if_command ($2, $4, $5); }
+       ;
+
+
+group_command: '{' compound_list '}'
+                       { $$ = make_group_command ($2); }
+       ;
+
+arith_command: ARITH_CMD
+                       { $$ = make_arith_command ($1); }
+       ;
+
+cond_command:  COND_START COND_CMD COND_END
+                       { $$ = $2; }
+       ; 
+
+elif_clause:   ELIF compound_list THEN compound_list
+                       { $$ = make_if_command ($2, $4, (COMMAND *)NULL); }
+       |       ELIF compound_list THEN compound_list ELSE compound_list
+                       { $$ = make_if_command ($2, $4, $6); }
+       |       ELIF compound_list THEN compound_list elif_clause
+                       { $$ = make_if_command ($2, $4, $5); }
+       ;
+
+case_clause:   pattern_list
+       |       case_clause_sequence pattern_list
+                       { $2->next = $1; $$ = $2; }
+       ;
+
+pattern_list:  newline_list pattern ')' compound_list
+                       { $$ = make_pattern_list ($2, $4); }
+       |       newline_list pattern ')' newline_list
+                       { $$ = make_pattern_list ($2, (COMMAND *)NULL); }
+       |       newline_list '(' pattern ')' compound_list
+                       { $$ = make_pattern_list ($3, $5); }
+       |       newline_list '(' pattern ')' newline_list
+                       { $$ = make_pattern_list ($3, (COMMAND *)NULL); }
+       ;
+
+case_clause_sequence:  pattern_list SEMI_SEMI
+       |       case_clause_sequence pattern_list SEMI_SEMI
+                       { $2->next = $1; $$ = $2; }
+       ;
+
+pattern:       WORD
+                       { $$ = make_word_list ($1, (WORD_LIST *)NULL); }
+       |       pattern '|' WORD
+                       { $$ = make_word_list ($3, $1); }
+       ;
+
+/* A list allows leading or trailing newlines and
+   newlines as operators (equivalent to semicolons).
+   It must end with a newline or semicolon.
+   Lists are used within commands such as if, for, while.  */
+
+list:          newline_list list0
+                       {
+                         $$ = $2;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                        }
+       ;
+
+compound_list: list
+       |       newline_list list1
+                       {
+                         $$ = $2;
+                       }
+       ;
+
+list0:         list1 '\n' newline_list
+       |       list1 '&' newline_list
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
+                         else
+                           $$ = command_connect ($1, (COMMAND *)NULL, '&');
+                       }
+       |       list1 ';' newline_list
+
+       ;
+
+list1:         list1 AND_AND newline_list list1
+                       { $$ = command_connect ($1, $4, AND_AND); }
+       |       list1 OR_OR newline_list list1
+                       { $$ = command_connect ($1, $4, OR_OR); }
+       |       list1 '&' newline_list list1
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, $4, '&');
+                         else
+                           $$ = command_connect ($1, $4, '&');
+                       }
+       |       list1 ';' newline_list list1
+                       { $$ = command_connect ($1, $4, ';'); }
+       |       list1 '\n' newline_list list1
+                       { $$ = command_connect ($1, $4, ';'); }
+       |       pipeline_command
+                       { $$ = $1; }
+       ;
+
+simple_list_terminator:        '\n'
+       |       yacc_EOF
+       ;
+
+list_terminator:'\n'
+               { $$ = '\n'; }
+       |       ';'
+               { $$ = ';'; }
+       |       yacc_EOF
+               { $$ = yacc_EOF; }
+       ;
+
+newline_list:
+       |       newline_list '\n'
+       ;
+
+/* A simple_list is a list that contains no significant newlines
+   and no leading or trailing newlines.  Newlines are allowed
+   only following operators, where they are not significant.
+
+   This is what an inputunit consists of.  */
+
+simple_list:   simple_list1
+                       {
+                         $$ = $1;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                       }
+       |       simple_list1 '&'
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, (COMMAND *)NULL, '&');
+                         else
+                           $$ = command_connect ($1, (COMMAND *)NULL, '&');
+                         if (need_here_doc)
+                           gather_here_documents ();
+                       }
+       |       simple_list1 ';'
+                       {
+                         $$ = $1;
+                         if (need_here_doc)
+                           gather_here_documents ();
+                       }
+       ;
+
+simple_list1:  simple_list1 AND_AND newline_list simple_list1
+                       { $$ = command_connect ($1, $4, AND_AND); }
+       |       simple_list1 OR_OR newline_list simple_list1
+                       { $$ = command_connect ($1, $4, OR_OR); }
+       |       simple_list1 '&' simple_list1
+                       {
+                         if ($1->type == cm_connection)
+                           $$ = connect_async_list ($1, $3, '&');
+                         else
+                           $$ = command_connect ($1, $3, '&');
+                       }
+       |       simple_list1 ';' simple_list1
+                       { $$ = command_connect ($1, $3, ';'); }
+
+       |       pipeline_command
+                       { $$ = $1; }
+       ;
+
+pipeline_command: pipeline
+                       { $$ = $1; }
+       |       BANG pipeline
+                       {
+                         if ($2)
+                           $2->flags |= CMD_INVERT_RETURN;
+                         $$ = $2;
+                       }
+       |       timespec pipeline
+                       {
+                         if ($2)
+                           $2->flags |= $1;
+                         $$ = $2;
+                       }
+       |       timespec BANG pipeline
+                       {
+                         if ($3)
+                           $3->flags |= $1|CMD_INVERT_RETURN;
+                         $$ = $3;
+                       }
+       |       BANG timespec pipeline
+                       {
+                         if ($3)
+                           $3->flags |= $2|CMD_INVERT_RETURN;
+                         $$ = $3;
+                       }
+       |       timespec list_terminator
+                       {
+                         ELEMENT x;
+
+                         /* Boy, this is unclean.  `time' by itself can
+                            time a null command.  We cheat and push a
+                            newline back if the list_terminator was a newline
+                            to avoid the double-newline problem (one to
+                            terminate this, one to terminate the command) */
+                         x.word = 0;
+                         x.redirect = 0;
+                         $$ = make_simple_command (x, (COMMAND *)NULL);
+                         $$->flags |= $1;
+                         /* XXX - let's cheat and push a newline back */
+                         if ($2 == '\n')
+                           token_to_read = '\n';
+                       }
+                       
+       ;
+
+pipeline:
+               pipeline '|' newline_list pipeline
+                       { $$ = command_connect ($1, $4, '|'); }
+       |       command
+                       { $$ = $1; }
+       ;
+
+timespec:      TIME
+                       { $$ = CMD_TIME_PIPELINE; }
+       |       TIME TIMEOPT
+                       { $$ = CMD_TIME_PIPELINE|CMD_TIME_POSIX; }
+       ;
+%%
+
+/* Possible states for the parser that require it to do special things. */
+#define PST_CASEPAT    0x0001          /* in a case pattern list */
+#define PST_ALEXPNEXT  0x0002          /* expand next word for aliases */
+#define PST_ALLOWOPNBRC        0x0004          /* allow open brace for function def */
+#define PST_NEEDCLOSBRC        0x0008          /* need close brace */
+#define PST_DBLPAREN   0x0010          /* double-paren parsing */
+#define PST_SUBSHELL   0x0020          /* ( ... ) subshell */
+#define PST_CMDSUBST   0x0040          /* $( ... ) command substitution */
+#define PST_CASESTMT   0x0080          /* parsing a case statement */
+#define PST_CONDCMD    0x0100          /* parsing a [[...]] command */
+#define PST_CONDEXPR   0x0200          /* parsing the guts of [[...]] */
+#define PST_ARITHFOR   0x0400          /* parsing an arithmetic for command */
+#define PST_ALEXPAND   0x0800          /* OK to expand aliases - unused */
+#define PST_CMDTOKEN   0x1000          /* command token OK - unused */
+#define PST_COMPASSIGN 0x2000          /* parsing x=(...) compound assignment */
+#define PST_ASSIGNOK   0x4000          /* assignment statement ok in this context */
+
+/* Initial size to allocate for tokens, and the
+   amount to grow them by. */
+#define TOKEN_DEFAULT_INITIAL_SIZE 496
+#define TOKEN_DEFAULT_GROW_SIZE 512
+
+/* Should we call prompt_again? */
+#define SHOULD_PROMPT() \
+  (interactive && (bash_input.type == st_stdin || bash_input.type == st_stream))
+
+#if defined (ALIAS)
+#  define expanding_alias() (pushed_string_list && pushed_string_list->expander)
+#else
+#  define expanding_alias() 0
+#endif
+
+/* The token currently being read. */
+static int current_token;
+
+/* The last read token, or NULL.  read_token () uses this for context
+   checking. */
+static int last_read_token;
+
+/* The token read prior to last_read_token. */
+static int token_before_that;
+
+/* The token read prior to token_before_that. */
+static int two_tokens_ago;
+
+/* The current parser state. */
+static int parser_state;
+
+/* Global var is non-zero when end of file has been reached. */
+int EOF_Reached = 0;
+
+#ifdef DEBUG
+static void
+debug_parser (i)
+     int i;
+{
+#if YYDEBUG != 0
+  yydebug = i;
+#endif
+}
+#endif
+
+/* yy_getc () returns the next available character from input or EOF.
+   yy_ungetc (c) makes `c' the next character to read.
+   init_yy_io (get, unget, type, location) makes the function GET the
+   installed function for getting the next character, makes UNGET the
+   installed function for un-getting a character, sets the type of stream
+   (either string or file) from TYPE, and makes LOCATION point to where
+   the input is coming from. */
+
+/* Unconditionally returns end-of-file. */
+int
+return_EOF ()
+{
+  return (EOF);
+}
+
+/* Variable containing the current get and unget functions.
+   See ./input.h for a clearer description. */
+BASH_INPUT bash_input;
+
+/* Set all of the fields in BASH_INPUT to NULL.  Free bash_input.name if it
+   is non-null, avoiding a memory leak. */
+void
+initialize_bash_input ()
+{
+  bash_input.type = st_none;
+  FREE (bash_input.name);
+  bash_input.name = (char *)NULL;
+  bash_input.location.file = (FILE *)NULL;
+  bash_input.location.string = (char *)NULL;
+  bash_input.getter = (sh_cget_func_t *)NULL;
+  bash_input.ungetter = (sh_cunget_func_t *)NULL;
+}
+
+/* Set the contents of the current bash input stream from
+   GET, UNGET, TYPE, NAME, and LOCATION. */
+void
+init_yy_io (get, unget, type, name, location)
+     sh_cget_func_t *get;
+     sh_cunget_func_t *unget;
+     enum stream_type type;
+     const char *name;
+     INPUT_STREAM location;
+{
+  bash_input.type = type;
+  FREE (bash_input.name);
+  bash_input.name = name ? savestring (name) : (char *)NULL;
+
+  /* XXX */
+#if defined (CRAY)
+  memcpy((char *)&bash_input.location.string, (char *)&location.string, sizeof(location));
+#else
+  bash_input.location = location;
+#endif
+  bash_input.getter = get;
+  bash_input.ungetter = unget;
+}
+
+char *
+yy_input_name ()
+{
+  return (bash_input.name ? bash_input.name : "stdin");
+}
+
+/* Call this to get the next character of input. */
+static int
+yy_getc ()
+{
+  return (*(bash_input.getter)) ();
+}
+
+/* Call this to unget C.  That is, to make C the next character
+   to be read. */
+static int
+yy_ungetc (c)
+     int c;
+{
+  return (*(bash_input.ungetter)) (c);
+}
+
+#if defined (BUFFERED_INPUT)
+#ifdef INCLUDE_UNUSED
+int
+input_file_descriptor ()
+{
+  switch (bash_input.type)
+    {
+    case st_stream:
+      return (fileno (bash_input.location.file));
+    case st_bstream:
+      return (bash_input.location.buffered_fd);
+    case st_stdin:
+    default:
+      return (fileno (stdin));
+    }
+}
+#endif
+#endif /* BUFFERED_INPUT */
+
+/* **************************************************************** */
+/*                                                                 */
+/*               Let input be read from readline ().               */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (READLINE)
+char *current_readline_prompt = (char *)NULL;
+char *current_readline_line = (char *)NULL;
+int current_readline_line_index = 0;
+
+static int
+yy_readline_get ()
+{
+  SigHandler *old_sigint;
+  int line_len;
+  unsigned char c;
+
+  if (!current_readline_line)
+    {
+      if (!bash_readline_initialized)
+       initialize_readline ();
+
+#if defined (JOB_CONTROL)
+      if (job_control)
+       give_terminal_to (shell_pgrp, 0);
+#endif /* JOB_CONTROL */
+
+      old_sigint = (SigHandler *)NULL;
+      if (signal_is_ignored (SIGINT) == 0)
+       {
+         old_sigint = (SigHandler *)set_signal_handler (SIGINT, sigint_sighandler);
+         interrupt_immediately++;
+       }
+
+      current_readline_line = readline (current_readline_prompt ?
+                                         current_readline_prompt : "");
+
+      if (signal_is_ignored (SIGINT) == 0 && old_sigint)
+       {
+         interrupt_immediately--;
+         set_signal_handler (SIGINT, old_sigint);
+       }
+
+#if 0
+      /* Reset the prompt to the decoded value of prompt_string_pointer. */
+      reset_readline_prompt ();
+#endif
+
+      if (current_readline_line == 0)
+       return (EOF);
+
+      current_readline_line_index = 0;
+      line_len = strlen (current_readline_line);
+
+      current_readline_line = (char *)xrealloc (current_readline_line, 2 + line_len);
+      current_readline_line[line_len++] = '\n';
+      current_readline_line[line_len] = '\0';
+    }
+
+  if (current_readline_line[current_readline_line_index] == 0)
+    {
+      free (current_readline_line);
+      current_readline_line = (char *)NULL;
+      return (yy_readline_get ());
+    }
+  else
+    {
+      c = current_readline_line[current_readline_line_index++];
+      return (c);
+    }
+}
+
+static int
+yy_readline_unget (c)
+     int c;
+{
+  if (current_readline_line_index && current_readline_line)
+    current_readline_line[--current_readline_line_index] = c;
+  return (c);
+}
+
+void
+with_input_from_stdin ()
+{
+  INPUT_STREAM location;
+
+  if (bash_input.type != st_stdin && stream_on_stack (st_stdin) == 0)
+    {
+      location.string = current_readline_line;
+      init_yy_io (yy_readline_get, yy_readline_unget,
+                 st_stdin, "readline stdin", location);
+    }
+}
+
+#else  /* !READLINE */
+
+void
+with_input_from_stdin ()
+{
+  with_input_from_stream (stdin, "stdin");
+}
+#endif /* !READLINE */
+
+/* **************************************************************** */
+/*                                                                 */
+/*   Let input come from STRING.  STRING is zero terminated.       */
+/*                                                                 */
+/* **************************************************************** */
+
+static int
+yy_string_get ()
+{
+  register char *string;
+  register unsigned char c;
+
+  string = bash_input.location.string;
+
+  /* If the string doesn't exist, or is empty, EOF found. */
+  if (string && *string)
+    {
+      c = *string++;
+      bash_input.location.string = string;
+      return (c);
+    }
+  else
+    return (EOF);
+}
+
+static int
+yy_string_unget (c)
+     int c;
+{
+  *(--bash_input.location.string) = c;
+  return (c);
+}
+
+void
+with_input_from_string (string, name)
+     char *string;
+     const char *name;
+{
+  INPUT_STREAM location;
+
+  location.string = string;
+  init_yy_io (yy_string_get, yy_string_unget, st_string, name, location);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                  Let input come from STREAM.                    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* These two functions used to test the value of the HAVE_RESTARTABLE_SYSCALLS
+   define, and just use getc/ungetc if it was defined, but since bash
+   installs its signal handlers without the SA_RESTART flag, some signals
+   (like SIGCHLD, SIGWINCH, etc.) received during a read(2) will not cause
+   the read to be restarted.  We need to restart it ourselves. */
+
+static int
+yy_stream_get ()
+{
+  int result;
+
+  result = EOF;
+  if (bash_input.location.file)
+    {
+      if (interactive)
+       interrupt_immediately++;
+      result = getc_with_restart (bash_input.location.file);
+      if (interactive)
+       interrupt_immediately--;
+    }
+  return (result);
+}
+
+static int
+yy_stream_unget (c)
+     int c;
+{
+  return (ungetc_with_restart (c, bash_input.location.file));
+}
+
+void
+with_input_from_stream (stream, name)
+     FILE *stream;
+     const char *name;
+{
+  INPUT_STREAM location;
+
+  location.file = stream;
+  init_yy_io (yy_stream_get, yy_stream_unget, st_stream, name, location);
+}
+
+typedef struct stream_saver {
+  struct stream_saver *next;
+  BASH_INPUT bash_input;
+  int line;
+#if defined (BUFFERED_INPUT)
+  BUFFERED_STREAM *bstream;
+#endif /* BUFFERED_INPUT */
+} STREAM_SAVER;
+
+/* The globally known line number. */
+int line_number = 0;
+
+#if defined (COND_COMMAND)
+static int cond_lineno;
+static int cond_token;
+#endif
+
+STREAM_SAVER *stream_list = (STREAM_SAVER *)NULL;
+
+void
+push_stream (reset_lineno)
+     int reset_lineno;
+{
+  STREAM_SAVER *saver = (STREAM_SAVER *)xmalloc (sizeof (STREAM_SAVER));
+
+  xbcopy ((char *)&bash_input, (char *)&(saver->bash_input), sizeof (BASH_INPUT));
+
+#if defined (BUFFERED_INPUT)
+  saver->bstream = (BUFFERED_STREAM *)NULL;
+  /* If we have a buffered stream, clear out buffers[fd]. */
+  if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0)
+    saver->bstream = set_buffered_stream (bash_input.location.buffered_fd,
+                                         (BUFFERED_STREAM *)NULL);
+#endif /* BUFFERED_INPUT */
+
+  saver->line = line_number;
+  bash_input.name = (char *)NULL;
+  saver->next = stream_list;
+  stream_list = saver;
+  EOF_Reached = 0;
+  if (reset_lineno)
+    line_number = 0;
+}
+
+void
+pop_stream ()
+{
+  if (!stream_list)
+    EOF_Reached = 1;
+  else
+    {
+      STREAM_SAVER *saver = stream_list;
+
+      EOF_Reached = 0;
+      stream_list = stream_list->next;
+
+      init_yy_io (saver->bash_input.getter,
+                 saver->bash_input.ungetter,
+                 saver->bash_input.type,
+                 saver->bash_input.name,
+                 saver->bash_input.location);
+
+#if defined (BUFFERED_INPUT)
+      /* If we have a buffered stream, restore buffers[fd]. */
+      /* If the input file descriptor was changed while this was on the
+        save stack, update the buffered fd to the new file descriptor and
+        re-establish the buffer <-> bash_input fd correspondence. */
+      if (bash_input.type == st_bstream && bash_input.location.buffered_fd >= 0)
+       {
+         if (bash_input_fd_changed)
+           {
+             bash_input_fd_changed = 0;
+             if (default_buffered_input >= 0)
+               {
+                 bash_input.location.buffered_fd = default_buffered_input;
+                 saver->bstream->b_fd = default_buffered_input;
+                 SET_CLOSE_ON_EXEC (default_buffered_input);
+               }
+           }
+         /* XXX could free buffered stream returned as result here. */
+         set_buffered_stream (bash_input.location.buffered_fd, saver->bstream);
+       }
+#endif /* BUFFERED_INPUT */
+
+      line_number = saver->line;
+
+      FREE (saver->bash_input.name);
+      free (saver);
+    }
+}
+
+/* Return 1 if a stream of type TYPE is saved on the stack. */
+int
+stream_on_stack (type)
+     enum stream_type type;
+{
+  register STREAM_SAVER *s;
+
+  for (s = stream_list; s; s = s->next)
+    if (s->bash_input.type == type)
+      return 1;
+  return 0;
+}
+
+/* Save the current token state and return it in a malloced array. */
+int *
+save_token_state ()
+{
+  int *ret;
+
+  ret = (int *)xmalloc (3 * sizeof (int));
+  ret[0] = last_read_token;
+  ret[1] = token_before_that;
+  ret[2] = two_tokens_ago;
+  return ret;
+}
+
+void
+restore_token_state (ts)
+     int *ts;
+{
+  if (ts == 0)
+    return;
+  last_read_token = ts[0];
+  token_before_that = ts[1];
+  two_tokens_ago = ts[2];
+}
+
+/*
+ * This is used to inhibit alias expansion and reserved word recognition
+ * inside case statement pattern lists.  A `case statement pattern list' is:
+ *
+ *     everything between the `in' in a `case word in' and the next ')'
+ *     or `esac'
+ *     everything between a `;;' and the next `)' or `esac'
+ */
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+
+#define END_OF_ALIAS 0
+
+/*
+ * Pseudo-global variables used in implementing token-wise alias expansion.
+ */
+
+/*
+ * Pushing and popping strings.  This works together with shell_getc to
+ * implement alias expansion on a per-token basis.
+ */
+
+typedef struct string_saver {
+  struct string_saver *next;
+  int expand_alias;  /* Value to set expand_alias to when string is popped. */
+  char *saved_line;
+#if defined (ALIAS)
+  alias_t *expander;   /* alias that caused this line to be pushed. */
+#endif
+  int saved_line_size, saved_line_index, saved_line_terminator;
+} STRING_SAVER;
+
+STRING_SAVER *pushed_string_list = (STRING_SAVER *)NULL;
+
+/*
+ * Push the current shell_input_line onto a stack of such lines and make S
+ * the current input.  Used when expanding aliases.  EXPAND is used to set
+ * the value of expand_next_token when the string is popped, so that the
+ * word after the alias in the original line is handled correctly when the
+ * alias expands to multiple words.  TOKEN is the token that was expanded
+ * into S; it is saved and used to prevent infinite recursive expansion.
+ */
+static void
+push_string (s, expand, ap)
+     char *s;
+     int expand;
+     alias_t *ap;
+{
+  STRING_SAVER *temp = (STRING_SAVER *)xmalloc (sizeof (STRING_SAVER));
+
+  temp->expand_alias = expand;
+  temp->saved_line = shell_input_line;
+  temp->saved_line_size = shell_input_line_size;
+  temp->saved_line_index = shell_input_line_index;
+  temp->saved_line_terminator = shell_input_line_terminator;
+#if defined (ALIAS)
+  temp->expander = ap;
+#endif
+  temp->next = pushed_string_list;
+  pushed_string_list = temp;
+
+#if defined (ALIAS)
+  if (ap)
+    ap->flags |= AL_BEINGEXPANDED;
+#endif
+
+  shell_input_line = s;
+  shell_input_line_size = strlen (s);
+  shell_input_line_index = 0;
+  shell_input_line_terminator = '\0';
+#if 0
+  parser_state &= ~PST_ALEXPNEXT;      /* XXX */
+#endif
+
+  set_line_mbstate ();
+}
+
+/*
+ * Make the top of the pushed_string stack be the current shell input.
+ * Only called when there is something on the stack.  Called from shell_getc
+ * when it thinks it has consumed the string generated by an alias expansion
+ * and needs to return to the original input line.
+ */
+static void
+pop_string ()
+{
+  STRING_SAVER *t;
+
+  FREE (shell_input_line);
+  shell_input_line = pushed_string_list->saved_line;
+  shell_input_line_index = pushed_string_list->saved_line_index;
+  shell_input_line_size = pushed_string_list->saved_line_size;
+  shell_input_line_terminator = pushed_string_list->saved_line_terminator;
+
+  if (pushed_string_list->expand_alias)
+    parser_state |= PST_ALEXPNEXT;
+  else
+    parser_state &= ~PST_ALEXPNEXT;
+
+  t = pushed_string_list;
+  pushed_string_list = pushed_string_list->next;
+
+#if defined (ALIAS)
+  if (t->expander)
+    t->expander->flags &= ~AL_BEINGEXPANDED;
+#endif
+
+  free ((char *)t);
+
+  set_line_mbstate ();
+}
+
+static void
+free_string_list ()
+{
+  register STRING_SAVER *t, *t1;
+
+  for (t = pushed_string_list; t; )
+    {
+      t1 = t->next;
+      FREE (t->saved_line);
+#if defined (ALIAS)
+      if (t->expander)
+       t->expander->flags &= ~AL_BEINGEXPANDED;
+#endif
+      free ((char *)t);
+      t = t1;
+    }
+  pushed_string_list = (STRING_SAVER *)NULL;
+}
+
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+void
+free_pushed_string_input ()
+{
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  free_string_list ();
+#endif
+}
+
+/* Return a line of text, taken from wherever yylex () reads input.
+   If there is no more input, then we return NULL.  If REMOVE_QUOTED_NEWLINE
+   is non-zero, we remove unquoted \<newline> pairs.  This is used by
+   read_secondary_line to read here documents. */
+static char *
+read_a_line (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  static char *line_buffer = (char *)NULL;
+  static int buffer_size = 0;
+  int indx = 0, c, peekc, pass_next;
+
+#if defined (READLINE)
+  if (no_line_editing && SHOULD_PROMPT ())
+#else
+  if (SHOULD_PROMPT ())
+#endif
+    print_prompt ();
+
+  pass_next = 0;
+  while (1)
+    {
+      /* Allow immediate exit if interrupted during input. */
+      QUIT;
+
+      c = yy_getc ();
+
+      /* Ignore null bytes in input. */
+      if (c == 0)
+       {
+#if 0
+         internal_warning ("read_a_line: ignored null byte in input");
+#endif
+         continue;
+       }
+
+      /* If there is no more input, then we return NULL. */
+      if (c == EOF)
+       {
+         if (interactive && bash_input.type == st_stream)
+           clearerr (stdin);
+         if (indx == 0)
+           return ((char *)NULL);
+         c = '\n';
+       }
+
+      /* `+2' in case the final character in the buffer is a newline. */
+      RESIZE_MALLOCED_BUFFER (line_buffer, indx, 2, buffer_size, 128);
+
+      /* IF REMOVE_QUOTED_NEWLINES is non-zero, we are reading a
+        here document with an unquoted delimiter.  In this case,
+        the line will be expanded as if it were in double quotes.
+        We allow a backslash to escape the next character, but we
+        need to treat the backslash specially only if a backslash
+        quoting a backslash-newline pair appears in the line. */
+      if (pass_next)
+       {
+         line_buffer[indx++] = c;
+         pass_next = 0;
+       }
+      else if (c == '\\' && remove_quoted_newline)
+       {
+         peekc = yy_getc ();
+         if (peekc == '\n')
+           {
+             line_number++;
+             continue; /* Make the unquoted \<newline> pair disappear. */
+           }
+         else
+           {
+             yy_ungetc (peekc);
+             pass_next = 1;
+             line_buffer[indx++] = c;          /* Preserve the backslash. */
+           }
+       }
+      else
+       line_buffer[indx++] = c;
+
+      if (c == '\n')
+       {
+         line_buffer[indx] = '\0';
+         return (line_buffer);
+       }
+    }
+}
+
+/* Return a line as in read_a_line (), but insure that the prompt is
+   the secondary prompt.  This is used to read the lines of a here
+   document.  REMOVE_QUOTED_NEWLINE is non-zero if we should remove
+   newlines quoted with backslashes while reading the line.  It is
+   non-zero unless the delimiter of the here document was quoted. */
+char *
+read_secondary_line (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  prompt_string_pointer = &ps2_prompt;
+  if (SHOULD_PROMPT())
+    prompt_again ();
+  return (read_a_line (remove_quoted_newline));
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                             YYLEX ()                            */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Reserved words.  These are only recognized as the first word of a
+   command. */
+STRING_INT_ALIST word_token_alist[] = {
+  { "if", IF },
+  { "then", THEN },
+  { "else", ELSE },
+  { "elif", ELIF },
+  { "fi", FI },
+  { "case", CASE },
+  { "esac", ESAC },
+  { "for", FOR },
+#if defined (SELECT_COMMAND)
+  { "select", SELECT },
+#endif
+  { "while", WHILE },
+  { "until", UNTIL },
+  { "do", DO },
+  { "done", DONE },
+  { "in", IN },
+  { "function", FUNCTION },
+#if defined (COMMAND_TIMING)
+  { "time", TIME },
+#endif
+  { "{", '{' },
+  { "}", '}' },
+  { "!", BANG },
+#if defined (COND_COMMAND)
+  { "[[", COND_START },
+  { "]]", COND_END },
+#endif
+  { (char *)NULL, 0}
+};
+
+/* other tokens that can be returned by read_token() */
+STRING_INT_ALIST other_token_alist[] = {
+  /* Multiple-character tokens with special values */
+  { "-p", TIMEOPT },
+  { "&&", AND_AND },
+  { "||", OR_OR },
+  { ">>", GREATER_GREATER },
+  { "<<", LESS_LESS },
+  { "<&", LESS_AND },
+  { ">&", GREATER_AND },
+  { ";;", SEMI_SEMI },
+  { "<<-", LESS_LESS_MINUS },
+  { "<<<", LESS_LESS_LESS },
+  { "&>", AND_GREATER },
+  { "<>", LESS_GREATER },
+  { ">|", GREATER_BAR },
+  { "EOF", yacc_EOF },
+  /* Tokens whose value is the character itself */
+  { ">", '>' },
+  { "<", '<' },
+  { "-", '-' },
+  { "{", '{' },
+  { "}", '}' },
+  { ";", ';' },
+  { "(", '(' },
+  { ")", ')' },
+  { "|", '|' },
+  { "&", '&' },
+  { "newline", '\n' },
+  { (char *)NULL, 0}
+};
+
+/* others not listed here:
+       WORD                    look at yylval.word
+       ASSIGNMENT_WORD         look at yylval.word
+       NUMBER                  look at yylval.number
+       ARITH_CMD               look at yylval.word_list
+       ARITH_FOR_EXPRS         look at yylval.word_list
+       COND_CMD                look at yylval.command
+*/
+
+/* These are used by read_token_word, but appear up here so that shell_getc
+   can use them to decide when to add otherwise blank lines to the history. */
+
+/* The primary delimiter stack. */
+struct dstack dstack = {  (char *)NULL, 0, 0 };
+
+/* A temporary delimiter stack to be used when decoding prompt strings.
+   This is needed because command substitutions in prompt strings (e.g., PS2)
+   can screw up the parser's quoting state. */
+static struct dstack temp_dstack = { (char *)NULL, 0, 0 };
+
+/* Macro for accessing the top delimiter on the stack.  Returns the
+   delimiter or zero if none. */
+#define current_delimiter(ds) \
+  (ds.delimiter_depth ? ds.delimiters[ds.delimiter_depth - 1] : 0)
+
+#define push_delimiter(ds, character) \
+  do \
+    { \
+      if (ds.delimiter_depth + 2 > ds.delimiter_space) \
+       ds.delimiters = (char *)xrealloc \
+         (ds.delimiters, (ds.delimiter_space += 10) * sizeof (char)); \
+      ds.delimiters[ds.delimiter_depth] = character; \
+      ds.delimiter_depth++; \
+    } \
+  while (0)
+
+#define pop_delimiter(ds)      ds.delimiter_depth--
+
+/* Return the next shell input character.  This always reads characters
+   from shell_input_line; when that line is exhausted, it is time to
+   read the next line.  This is called by read_token when the shell is
+   processing normal command input. */
+
+/* This implements one-character lookahead/lookbehind across physical input
+   lines, to avoid something being lost because it's pushed back with
+   shell_ungetc when we're at the start of a line. */
+static int eol_ungetc_lookahead = 0;
+
+static int
+shell_getc (remove_quoted_newline)
+     int remove_quoted_newline;
+{
+  register int i;
+  int c;
+  unsigned char uc;
+  static int mustpop = 0;
+
+  QUIT;
+
+  if (eol_ungetc_lookahead)
+    {
+      c = eol_ungetc_lookahead;
+      eol_ungetc_lookahead = 0;
+      return (c);
+    }
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  /* If shell_input_line[shell_input_line_index] == 0, but there is
+     something on the pushed list of strings, then we don't want to go
+     off and get another line.  We let the code down below handle it. */
+
+  if (!shell_input_line || ((!shell_input_line[shell_input_line_index]) &&
+                           (pushed_string_list == (STRING_SAVER *)NULL)))
+#else /* !ALIAS && !DPAREN_ARITHMETIC */
+  if (!shell_input_line || !shell_input_line[shell_input_line_index])
+#endif /* !ALIAS && !DPAREN_ARITHMETIC */
+    {
+      line_number++;
+
+    restart_read:
+
+      /* Allow immediate exit if interrupted during input. */
+      QUIT;
+
+      i = 0;
+      shell_input_line_terminator = 0;
+
+      /* If the shell is interatctive, but not currently printing a prompt
+         (interactive_shell && interactive == 0), we don't want to print
+         notifies or cleanup the jobs -- we want to defer it until we do
+         print the next prompt. */
+      if (interactive_shell == 0 || SHOULD_PROMPT())
+       {
+#if defined (JOB_CONTROL)
+      /* This can cause a problem when reading a command as the result
+        of a trap, when the trap is called from flush_child.  This call
+        had better not cause jobs to disappear from the job table in
+        that case, or we will have big trouble. */
+         notify_and_cleanup ();
+#else /* !JOB_CONTROL */
+         cleanup_dead_jobs ();
+#endif /* !JOB_CONTROL */
+       }
+
+#if defined (READLINE)
+      if (no_line_editing && SHOULD_PROMPT())
+#else
+      if (SHOULD_PROMPT())
+#endif
+       print_prompt ();
+
+      if (bash_input.type == st_stream)
+       clearerr (stdin);
+
+      while (1)
+       {
+         c = yy_getc ();
+
+         /* Allow immediate exit if interrupted during input. */
+         QUIT;
+
+         if (c == '\0')
+           {
+#if 0
+             internal_warning ("shell_getc: ignored null byte in input");
+#endif
+             continue;
+           }
+
+         RESIZE_MALLOCED_BUFFER (shell_input_line, i, 2, shell_input_line_size, 256);
+
+         if (c == EOF)
+           {
+             if (bash_input.type == st_stream)
+               clearerr (stdin);
+
+             if (i == 0)
+               shell_input_line_terminator = EOF;
+
+             shell_input_line[i] = '\0';
+             break;
+           }
+
+         shell_input_line[i++] = c;
+
+         if (c == '\n')
+           {
+             shell_input_line[--i] = '\0';
+             current_command_line_count++;
+             break;
+           }
+       }
+
+      shell_input_line_index = 0;
+      shell_input_line_len = i;                /* == strlen (shell_input_line) */
+
+      set_line_mbstate ();
+
+#if defined (HISTORY)
+      if (remember_on_history && shell_input_line && shell_input_line[0])
+       {
+         char *expansions;
+#  if defined (BANG_HISTORY)
+         int old_hist;
+
+         /* If the current delimiter is a single quote, we should not be
+            performing history expansion, even if we're on a different
+            line from the original single quote. */
+         old_hist = history_expansion_inhibited;
+         if (current_delimiter (dstack) == '\'')
+           history_expansion_inhibited = 1;
+#  endif
+         expansions = pre_process_line (shell_input_line, 1, 1);
+#  if defined (BANG_HISTORY)
+         history_expansion_inhibited = old_hist;
+#  endif
+         if (expansions != shell_input_line)
+           {
+             free (shell_input_line);
+             shell_input_line = expansions;
+             shell_input_line_len = shell_input_line ?
+                                       strlen (shell_input_line) : 0;
+             if (!shell_input_line_len)
+               current_command_line_count--;
+
+             /* We have to force the xrealloc below because we don't know
+                the true allocated size of shell_input_line anymore. */
+             shell_input_line_size = shell_input_line_len;
+
+             set_line_mbstate ();
+           }
+       }
+      /* Try to do something intelligent with blank lines encountered while
+        entering multi-line commands.  XXX - this is grotesque */
+      else if (remember_on_history && shell_input_line &&
+              shell_input_line[0] == '\0' &&
+              current_command_line_count > 1)
+       {
+         if (current_delimiter (dstack))
+           /* We know shell_input_line[0] == 0 and we're reading some sort of
+              quoted string.  This means we've got a line consisting of only
+              a newline in a quoted string.  We want to make sure this line
+              gets added to the history. */
+           maybe_add_history (shell_input_line);
+         else
+           {
+             char *hdcs;
+             hdcs = history_delimiting_chars ();
+             if (hdcs && hdcs[0] == ';')
+               maybe_add_history (shell_input_line);
+           }
+       }
+
+#endif /* HISTORY */
+
+      if (shell_input_line)
+       {
+         /* Lines that signify the end of the shell's input should not be
+            echoed. */
+         if (echo_input_at_read && (shell_input_line[0] ||
+                                    shell_input_line_terminator != EOF))
+           fprintf (stderr, "%s\n", shell_input_line);
+       }
+      else
+       {
+         shell_input_line_size = 0;
+         prompt_string_pointer = &current_prompt_string;
+         if (SHOULD_PROMPT ())
+           prompt_again ();
+         goto restart_read;
+       }
+
+      /* Add the newline to the end of this string, iff the string does
+        not already end in an EOF character.  */
+      if (shell_input_line_terminator != EOF)
+       {
+         if (shell_input_line_len + 3 > shell_input_line_size)
+           shell_input_line = (char *)xrealloc (shell_input_line,
+                                       1 + (shell_input_line_size += 2));
+
+         shell_input_line[shell_input_line_len] = '\n';
+         shell_input_line[shell_input_line_len + 1] = '\0';
+
+         set_line_mbstate ();
+       }
+    }
+
+  uc = shell_input_line[shell_input_line_index];
+
+  if (uc)
+    shell_input_line_index++;
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  /* If UC is NULL, we have reached the end of the current input string.  If
+     pushed_string_list is non-empty, it's time to pop to the previous string
+     because we have fully consumed the result of the last alias expansion.
+     Do it transparently; just return the next character of the string popped
+     to. */
+  if (!uc && (pushed_string_list != (STRING_SAVER *)NULL))
+    {
+      pop_string ();
+      uc = shell_input_line[shell_input_line_index];
+      if (uc)
+       shell_input_line_index++;
+    }
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+  if MBTEST(uc == '\\' && remove_quoted_newline && shell_input_line[shell_input_line_index] == '\n')
+    {
+       if (SHOULD_PROMPT ())
+         prompt_again ();
+       line_number++;
+       goto restart_read;
+    }
+
+  if (!uc && shell_input_line_terminator == EOF)
+    return ((shell_input_line_index != 0) ? '\n' : EOF);
+
+  return (uc);
+}
+
+/* Put C back into the input for the shell.  This might need changes for
+   HANDLE_MULTIBYTE around EOLs.  Since we (currently) never push back a
+   character different than we read, shell_input_line_property doesn't need
+   to change when manipulating shell_input_line.  The define for
+   last_shell_getc_is_singlebyte should take care of it, though. */
+static void
+shell_ungetc (c)
+     int c;
+{
+  if (shell_input_line && shell_input_line_index)
+    shell_input_line[--shell_input_line_index] = c;
+  else
+    eol_ungetc_lookahead = c;
+}
+
+#ifdef INCLUDE_UNUSED
+/* Back the input pointer up by one, effectively `ungetting' a character. */
+static void
+shell_ungetchar ()
+{
+  if (shell_input_line && shell_input_line_index)
+    shell_input_line_index--;
+}
+#endif
+
+/* Discard input until CHARACTER is seen, then push that character back
+   onto the input stream. */
+static void
+discard_until (character)
+     int character;
+{
+  int c;
+
+  while ((c = shell_getc (0)) != EOF && c != character)
+    ;
+
+  if (c != EOF)
+    shell_ungetc (c);
+}
+
+void
+execute_prompt_command (command)
+     char *command;
+{
+  char *last_lastarg;
+  sh_parser_state_t ps;
+
+  save_parser_state (&ps);
+  last_lastarg = get_string_value ("_");
+  if (last_lastarg)
+    last_lastarg = savestring (last_lastarg);
+
+  parse_and_execute (savestring (command), "PROMPT_COMMAND", SEVAL_NONINT|SEVAL_NOHIST);
+
+  restore_parser_state (&ps);
+  bind_variable ("_", last_lastarg, 0);
+  FREE (last_lastarg);
+
+  if (token_to_read == '\n')   /* reset_parser was called */
+    token_to_read = 0;
+}
+
+/* Place to remember the token.  We try to keep the buffer
+   at a reasonable size, but it can grow. */
+static char *token = (char *)NULL;
+
+/* Current size of the token buffer. */
+static int token_buffer_size;
+
+/* Command to read_token () explaining what we want it to do. */
+#define READ 0
+#define RESET 1
+#define prompt_is_ps1 \
+      (!prompt_string_pointer || prompt_string_pointer == &ps1_prompt)
+
+/* Function for yyparse to call.  yylex keeps track of
+   the last two tokens read, and calls read_token.  */
+static int
+yylex ()
+{
+  if (interactive && (current_token == 0 || current_token == '\n'))
+    {
+      /* Before we print a prompt, we might have to check mailboxes.
+        We do this only if it is time to do so. Notice that only here
+        is the mail alarm reset; nothing takes place in check_mail ()
+        except the checking of mail.  Please don't change this. */
+      if (prompt_is_ps1 && time_to_check_mail ())
+       {
+         check_mail ();
+         reset_mail_timer ();
+       }
+
+      /* Avoid printing a prompt if we're not going to read anything, e.g.
+        after resetting the parser with read_token (RESET). */
+      if (token_to_read == 0 && SHOULD_PROMPT ())
+       prompt_again ();
+    }
+
+  two_tokens_ago = token_before_that;
+  token_before_that = last_read_token;
+  last_read_token = current_token;
+  current_token = read_token (READ);
+  return (current_token);
+}
+
+/* When non-zero, we have read the required tokens
+   which allow ESAC to be the next one read. */
+static int esacs_needed_count;
+
+void
+gather_here_documents ()
+{
+  int r = 0;
+  while (need_here_doc)
+    {
+      make_here_document (redir_stack[r++]);
+      need_here_doc--;
+    }
+}
+
+/* When non-zero, an open-brace used to create a group is awaiting a close
+   brace partner. */
+static int open_brace_count;
+
+#define command_token_position(token) \
+  (((token) == ASSIGNMENT_WORD) || \
+   ((token) != SEMI_SEMI && reserved_word_acceptable(token)))
+
+#define assignment_acceptable(token) \
+  (command_token_position(token) && ((parser_state & PST_CASEPAT) == 0))
+
+/* Check to see if TOKEN is a reserved word and return the token
+   value if it is. */
+#define CHECK_FOR_RESERVED_WORD(tok) \
+  do { \
+    if (!dollar_present && !quoted && \
+       reserved_word_acceptable (last_read_token)) \
+      { \
+       int i; \
+       for (i = 0; word_token_alist[i].word != (char *)NULL; i++) \
+         if (STREQ (tok, word_token_alist[i].word)) \
+           { \
+             if ((parser_state & PST_CASEPAT) && (word_token_alist[i].token != ESAC)) \
+               break; \
+             if (word_token_alist[i].token == TIME && time_command_acceptable () == 0) \
+               break; \
+             if (word_token_alist[i].token == ESAC) \
+               parser_state &= ~(PST_CASEPAT|PST_CASESTMT); \
+             else if (word_token_alist[i].token == CASE) \
+               parser_state |= PST_CASESTMT; \
+             else if (word_token_alist[i].token == COND_END) \
+               parser_state &= ~(PST_CONDCMD|PST_CONDEXPR); \
+             else if (word_token_alist[i].token == COND_START) \
+               parser_state |= PST_CONDCMD; \
+             else if (word_token_alist[i].token == '{') \
+               open_brace_count++; \
+             else if (word_token_alist[i].token == '}' && open_brace_count) \
+               open_brace_count--; \
+             return (word_token_alist[i].token); \
+           } \
+      } \
+  } while (0)
+
+#if defined (ALIAS)
+
+    /* OK, we have a token.  Let's try to alias expand it, if (and only if)
+       it's eligible.
+
+       It is eligible for expansion if EXPAND_ALIASES is set, and
+       the token is unquoted and the last token read was a command
+       separator (or expand_next_token is set), and we are currently
+       processing an alias (pushed_string_list is non-empty) and this
+       token is not the same as the current or any previously
+       processed alias.
+
+       Special cases that disqualify:
+        In a pattern list in a case statement (parser_state & PST_CASEPAT). */
+
+static char *
+mk_alexpansion (s)
+     char *s;
+{
+  int l;
+  char *r;
+
+  l = strlen (s);
+  r = xmalloc (l + 2);
+  strcpy (r, s);
+  if (r[l -1] != ' ')
+    r[l++] = ' ';
+  r[l] = '\0';
+  return r;
+}
+
+static int
+alias_expand_token (tokstr)
+     char *tokstr;
+{
+  char *expanded;
+  alias_t *ap;
+
+  if (((parser_state & PST_ALEXPNEXT) || command_token_position (last_read_token)) &&
+       (parser_state & PST_CASEPAT) == 0)
+    {
+      ap = find_alias (tokstr);
+
+      /* Currently expanding this token. */
+      if (ap && (ap->flags & AL_BEINGEXPANDED))
+       return (NO_EXPANSION);
+
+      /* mk_alexpansion puts an extra space on the end of the alias expansion,
+         so the lookahead by the parser works right.  If this gets changed,
+         make sure the code in shell_getc that deals with reaching the end of
+         an expanded alias is changed with it. */
+      expanded = ap ? mk_alexpansion (ap->value) : (char *)NULL;
+
+      if (expanded)
+       {
+         push_string (expanded, ap->flags & AL_EXPANDNEXT, ap);
+         return (RE_READ_TOKEN);
+       }
+      else
+       /* This is an eligible token that does not have an expansion. */
+       return (NO_EXPANSION);
+    }
+  return (NO_EXPANSION);
+}
+#endif /* ALIAS */
+
+static int
+time_command_acceptable ()
+{
+#if defined (COMMAND_TIMING)
+  switch (last_read_token)
+    {
+    case 0:
+    case ';':
+    case '\n':
+    case AND_AND:
+    case OR_OR:
+    case '&':
+    case DO:
+    case THEN:
+    case ELSE:
+    case '{':          /* } */
+    case '(':          /* ) */
+      return 1;
+    default:
+      return 0;
+    }
+#else
+  return 0;
+#endif /* COMMAND_TIMING */
+}
+
+/* Handle special cases of token recognition:
+       IN is recognized if the last token was WORD and the token
+       before that was FOR or CASE or SELECT.
+
+       DO is recognized if the last token was WORD and the token
+       before that was FOR or SELECT.
+
+       ESAC is recognized if the last token caused `esacs_needed_count'
+       to be set
+
+       `{' is recognized if the last token as WORD and the token
+       before that was FUNCTION, or if we just parsed an arithmetic
+       `for' command.
+
+       `}' is recognized if there is an unclosed `{' present.
+
+       `-p' is returned as TIMEOPT if the last read token was TIME.
+
+       ']]' is returned as COND_END if the parser is currently parsing
+       a conditional expression ((parser_state & PST_CONDEXPR) != 0)
+
+       `time' is returned as TIME if and only if it is immediately
+       preceded by one of `;', `\n', `||', `&&', or `&'.
+*/
+
+static int
+special_case_tokens (tokstr)
+     char *tokstr;
+{
+  if ((last_read_token == WORD) &&
+#if defined (SELECT_COMMAND)
+      ((token_before_that == FOR) || (token_before_that == CASE) || (token_before_that == SELECT)) &&
+#else
+      ((token_before_that == FOR) || (token_before_that == CASE)) &&
+#endif
+      (tokstr[0] == 'i' && tokstr[1] == 'n' && tokstr[2] == 0))
+    {
+      if (token_before_that == CASE)
+       {
+         parser_state |= PST_CASEPAT;
+         esacs_needed_count++;
+       }
+      return (IN);
+    }
+
+  if (last_read_token == WORD &&
+#if defined (SELECT_COMMAND)
+      (token_before_that == FOR || token_before_that == SELECT) &&
+#else
+      (token_before_that == FOR) &&
+#endif
+      (tokstr[0] == 'd' && tokstr[1] == 'o' && tokstr[2] == '\0'))
+    return (DO);
+
+  /* Ditto for ESAC in the CASE case.
+     Specifically, this handles "case word in esac", which is a legal
+     construct, certainly because someone will pass an empty arg to the
+     case construct, and we don't want it to barf.  Of course, we should
+     insist that the case construct has at least one pattern in it, but
+     the designers disagree. */
+  if (esacs_needed_count)
+    {
+      esacs_needed_count--;
+      if (STREQ (tokstr, "esac"))
+       {
+         parser_state &= ~PST_CASEPAT;
+         return (ESAC);
+       }
+    }
+
+  /* The start of a shell function definition. */
+  if (parser_state & PST_ALLOWOPNBRC)
+    {
+      parser_state &= ~PST_ALLOWOPNBRC;
+      if (tokstr[0] == '{' && tokstr[1] == '\0')               /* } */
+       {
+         open_brace_count++;
+         function_bstart = line_number;
+         return ('{');                                 /* } */
+       }
+    }
+
+  /* We allow a `do' after a for ((...)) without an intervening
+     list_terminator */
+  if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == 'd' && tokstr[1] == 'o' && !tokstr[2])
+    return (DO);
+  if (last_read_token == ARITH_FOR_EXPRS && tokstr[0] == '{' && tokstr[1] == '\0')     /* } */
+    {
+      open_brace_count++;
+      return ('{');                    /* } */
+    }
+
+  if (open_brace_count && reserved_word_acceptable (last_read_token) && tokstr[0] == '}' && !tokstr[1])
+    {
+      open_brace_count--;              /* { */
+      return ('}');
+    }
+
+#if defined (COMMAND_TIMING)
+  /* Handle -p after `time'. */
+  if (last_read_token == TIME && tokstr[0] == '-' && tokstr[1] == 'p' && !tokstr[2])
+    return (TIMEOPT);
+#endif
+
+#if 0
+#if defined (COMMAND_TIMING)
+  if (STREQ (token, "time") && ((parser_state & PST_CASEPAT) == 0) && time_command_acceptable ())
+    return (TIME);
+#endif /* COMMAND_TIMING */
+#endif
+
+#if defined (COND_COMMAND) /* [[ */
+  if ((parser_state & PST_CONDEXPR) && tokstr[0] == ']' && tokstr[1] == ']' && tokstr[2] == '\0')
+    return (COND_END);
+#endif
+
+  return (-1);
+}
+
+/* Called from shell.c when Control-C is typed at top level.  Or
+   by the error rule at top level. */
+void
+reset_parser ()
+{
+  dstack.delimiter_depth = 0;  /* No delimiters found so far. */
+  open_brace_count = 0;
+
+  parser_state = 0;
+
+#if defined (ALIAS) || defined (DPAREN_ARITHMETIC)
+  if (pushed_string_list)
+    free_string_list ();
+#endif /* ALIAS || DPAREN_ARITHMETIC */
+
+  if (shell_input_line)
+    {
+      free (shell_input_line);
+      shell_input_line = (char *)NULL;
+      shell_input_line_size = shell_input_line_index = 0;
+    }
+
+  FREE (word_desc_to_read);
+  word_desc_to_read = (WORD_DESC *)NULL;
+
+  last_read_token = '\n';
+  token_to_read = '\n';
+}
+
+/* Read the next token.  Command can be READ (normal operation) or
+   RESET (to normalize state). */
+static int
+read_token (command)
+     int command;
+{
+  int character;               /* Current character. */
+  int peek_char;               /* Temporary look-ahead character. */
+  int result;                  /* The thing to return. */
+
+  if (command == RESET)
+    {
+      reset_parser ();
+      return ('\n');
+    }
+
+  if (token_to_read)
+    {
+      result = token_to_read;
+      if (token_to_read == WORD || token_to_read == ASSIGNMENT_WORD)
+       {
+         yylval.word = word_desc_to_read;
+         word_desc_to_read = (WORD_DESC *)NULL;
+       }
+      token_to_read = 0;
+      return (result);
+    }
+
+#if defined (COND_COMMAND)
+  if ((parser_state & (PST_CONDCMD|PST_CONDEXPR)) == PST_CONDCMD)
+    {
+      cond_lineno = line_number;
+      parser_state |= PST_CONDEXPR;
+      yylval.command = parse_cond_command ();
+      if (cond_token != COND_END)
+       {
+         cond_error ();
+         return (-1);
+       }
+      token_to_read = COND_END;
+      parser_state &= ~(PST_CONDEXPR|PST_CONDCMD);
+      return (COND_CMD);
+    }
+#endif
+
+#if defined (ALIAS)
+  /* This is a place to jump back to once we have successfully expanded a
+     token with an alias and pushed the string with push_string () */
+ re_read_token:
+#endif /* ALIAS */
+
+  /* Read a single word from input.  Start by skipping blanks. */
+  while ((character = shell_getc (1)) != EOF && whitespace (character))
+    ;
+
+  if (character == EOF)
+    {
+      EOF_Reached = 1;
+      return (yacc_EOF);
+    }
+
+  if MBTEST(character == '#' && (!interactive || interactive_comments))
+    {
+      /* A comment.  Discard until EOL or EOF, and then return a newline. */
+      discard_until ('\n');
+      shell_getc (0);
+      character = '\n';        /* this will take the next if statement and return. */
+    }
+
+  if (character == '\n')
+    {
+      /* If we're about to return an unquoted newline, we can go and collect
+        the text of any pending here document. */
+      if (need_here_doc)
+       gather_here_documents ();
+
+#if defined (ALIAS)
+      parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+      parser_state &= ~PST_ASSIGNOK;
+
+      return (character);
+    }
+
+  /* Shell meta-characters. */
+  if MBTEST(shellmeta (character) && ((parser_state & PST_DBLPAREN) == 0))
+    {
+#if defined (ALIAS)
+      /* Turn off alias tokenization iff this character sequence would
+        not leave us ready to read a command. */
+      if (character == '<' || character == '>')
+       parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+      parser_state &= ~PST_ASSIGNOK;
+
+      peek_char = shell_getc (1);
+      if (character == peek_char)
+       {
+         switch (character)
+           {
+           case '<':
+             /* If '<' then we could be at "<<" or at "<<-".  We have to
+                look ahead one more character. */
+             peek_char = shell_getc (1);
+             if (peek_char == '-')
+               return (LESS_LESS_MINUS);
+             else if (peek_char == '<')
+               return (LESS_LESS_LESS);
+             else
+               {
+                 shell_ungetc (peek_char);
+                 return (LESS_LESS);
+               }
+
+           case '>':
+             return (GREATER_GREATER);
+
+           case ';':
+             parser_state |= PST_CASEPAT;
+#if defined (ALIAS)
+             parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+
+             return (SEMI_SEMI);
+
+           case '&':
+             return (AND_AND);
+
+           case '|':
+             return (OR_OR);
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+           case '(':           /* ) */
+             result = parse_dparen (character);
+             if (result == -2)
+               break;
+             else
+               return result;
+#endif
+           }
+       }
+      else if MBTEST(character == '<' && peek_char == '&')
+       return (LESS_AND);
+      else if MBTEST(character == '>' && peek_char == '&')
+       return (GREATER_AND);
+      else if MBTEST(character == '<' && peek_char == '>')
+       return (LESS_GREATER);
+      else if MBTEST(character == '>' && peek_char == '|')
+       return (GREATER_BAR);
+      else if MBTEST(peek_char == '>' && character == '&')
+       return (AND_GREATER);
+
+      shell_ungetc (peek_char);
+
+      /* If we look like we are reading the start of a function
+        definition, then let the reader know about it so that
+        we will do the right thing with `{'. */
+      if MBTEST(character == ')' && last_read_token == '(' && token_before_that == WORD)
+       {
+         parser_state |= PST_ALLOWOPNBRC;
+#if defined (ALIAS)
+         parser_state &= ~PST_ALEXPNEXT;
+#endif /* ALIAS */
+         function_dstart = line_number;
+       }
+
+      /* case pattern lists may be preceded by an optional left paren.  If
+        we're not trying to parse a case pattern list, the left paren
+        indicates a subshell. */
+      if MBTEST(character == '(' && (parser_state & PST_CASEPAT) == 0) /* ) */
+       parser_state |= PST_SUBSHELL;
+      /*(*/
+      else if MBTEST((parser_state & PST_CASEPAT) && character == ')')
+       parser_state &= ~PST_CASEPAT;
+      /*(*/
+      else if MBTEST((parser_state & PST_SUBSHELL) && character == ')')
+       parser_state &= ~PST_SUBSHELL;
+
+#if defined (PROCESS_SUBSTITUTION)
+      /* Check for the constructs which introduce process substitution.
+        Shells running in `posix mode' don't do process substitution. */
+      if MBTEST(posixly_correct || ((character != '>' && character != '<') || peek_char != '(')) /*)*/
+#endif /* PROCESS_SUBSTITUTION */
+       return (character);
+    }
+
+  /* Hack <&- (close stdin) case.  Also <&N- (dup and close). */
+  if MBTEST(character == '-' && (last_read_token == LESS_AND || last_read_token == GREATER_AND))
+    return (character);
+
+  /* Okay, if we got this far, we have to read a word.  Read one,
+     and then check it against the known ones. */
+  result = read_token_word (character);
+#if defined (ALIAS)
+  if (result == RE_READ_TOKEN)
+    goto re_read_token;
+#endif
+  return result;
+}
+
+/*
+ * Match a $(...) or other grouping construct.  This has to handle embedded
+ * quoted strings ('', ``, "") and nested constructs.  It also must handle
+ * reprompting the user, if necessary, after reading a newline, and returning
+ * correct error values if it reads EOF.
+ */
+#define P_FIRSTCLOSE   0x01
+#define P_ALLOWESC     0x02
+#define P_DQUOTE       0x04
+#define P_COMMAND      0x08    /* parsing a command, so look for comments */
+
+static char matched_pair_error;
+static char *
+parse_matched_pair (qc, open, close, lenp, flags)
+     int qc;   /* `"' if this construct is within double quotes */
+     int open, close;
+     int *lenp, flags;
+{
+  int count, ch, was_dollar, in_comment, check_comment;
+  int pass_next_character, nestlen, ttranslen, start_lineno;
+  char *ret, *nestret, *ttrans;
+  int retind, retsize, rflags;
+
+  count = 1;
+  pass_next_character = was_dollar = in_comment = 0;
+  check_comment = (flags & P_COMMAND) && qc != '\'' && qc != '"' && (flags & P_DQUOTE) == 0;
+
+itrace("parse_matched_pair: check_comment set to %d", check_comment);
+
+  /* RFLAGS is the set of flags we want to pass to recursive calls. */
+  rflags = (qc == '"') ? P_DQUOTE : (flags & P_DQUOTE);
+
+  ret = (char *)xmalloc (retsize = 64);
+  retind = 0;
+
+  start_lineno = line_number;
+  while (count)
+    {
+#if 0
+      ch = shell_getc ((qc != '\'' || (flags & P_ALLOWESC)) && pass_next_character == 0);
+#else
+      ch = shell_getc (qc != '\'' && pass_next_character == 0);
+#endif
+      if (ch == EOF)
+       {
+         free (ret);
+         parser_error (start_lineno, _("unexpected EOF while looking for matching `%c'"), close);
+         EOF_Reached = 1;      /* XXX */
+         return (&matched_pair_error);
+       }
+
+      /* Possible reprompting. */
+      if (ch == '\n' && SHOULD_PROMPT ())
+       prompt_again ();
+
+      if (in_comment && ch == '\n')
+       in_comment = 0;
+      else if (check_comment && in_comment == 0 && ch == '#')
+{
+       in_comment = 1;
+itrace("parse_matched_pair: set in_comment to 1");
+}
+      if (pass_next_character)         /* last char was backslash */
+       {
+         pass_next_character = 0;
+         if (qc != '\'' && ch == '\n') /* double-quoted \<newline> disappears. */
+           {
+             if (retind > 0) retind--; /* swallow previously-added backslash */
+             continue;
+           }
+
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         if MBTEST(ch == CTLESC || ch == CTLNUL)
+           ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      else if MBTEST(ch == CTLESC || ch == CTLNUL)     /* special shell escapes */
+       {
+         RESIZE_MALLOCED_BUFFER (ret, retind, 2, retsize, 64);
+         ret[retind++] = CTLESC;
+         ret[retind++] = ch;
+         continue;
+       }
+      else if MBTEST(ch == close)              /* ending delimiter */
+       count--;
+#if 1
+      /* handle nested ${...} specially. */
+      else if MBTEST(open != close && was_dollar && open == '{' && ch == open) /* } */
+       count++;
+#endif
+      else if MBTEST(((flags & P_FIRSTCLOSE) == 0) && ch == open)      /* nested begin */
+       count++;
+
+      /* Add this character. */
+      RESIZE_MALLOCED_BUFFER (ret, retind, 1, retsize, 64);
+      ret[retind++] = ch;
+
+      if (open == '\'')                        /* '' inside grouping construct */
+       {
+         if MBTEST((flags & P_ALLOWESC) && ch == '\\')
+           pass_next_character++;
+         continue;
+       }
+
+      if MBTEST(ch == '\\')                    /* backslashes */
+       pass_next_character++;
+
+      if (open != close)               /* a grouping construct */
+       {
+         if MBTEST(shellquote (ch))
+           {
+             /* '', ``, or "" inside $(...) or other grouping construct. */
+             push_delimiter (dstack, ch);
+             if MBTEST(was_dollar && ch == '\'')       /* $'...' inside group */
+               nestret = parse_matched_pair (ch, ch, ch, &nestlen, P_ALLOWESC|rflags);
+             else
+               nestret = parse_matched_pair (ch, ch, ch, &nestlen, rflags);
+             pop_delimiter (dstack);
+             if (nestret == &matched_pair_error)
+               {
+                 free (ret);
+                 return &matched_pair_error;
+               }
+             if MBTEST(was_dollar && ch == '\'' && (extended_quote || (rflags & P_DQUOTE) == 0))
+               {
+                 /* Translate $'...' here. */
+                 ttrans = ansiexpand (nestret, 0, nestlen - 1, &ttranslen);
+                 xfree (nestret);
+
+                 if ((rflags & P_DQUOTE) == 0)
+                   {
+                     nestret = sh_single_quote (ttrans);
+                     free (ttrans);
+                     nestlen = strlen (nestret);
+                   }
+                 else
+                   {
+                     nestret = ttrans;
+                     nestlen = ttranslen;
+                   }
+                 retind -= 2;          /* back up before the $' */
+               }
+             else if MBTEST(was_dollar && ch == '"' && (extended_quote || (rflags & P_DQUOTE) == 0))
+               {
+                 /* Locale expand $"..." here. */
+                 ttrans = localeexpand (nestret, 0, nestlen - 1, start_lineno, &ttranslen);
+                 xfree (nestret);
+
+                 nestret = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 free (ttrans);
+                 nestlen = ttranslen + 2;
+                 retind -= 2;          /* back up before the $" */
+               }
+
+             if (nestlen)
+               {
+                 RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64);
+                 strcpy (ret + retind, nestret);
+                 retind += nestlen;
+               }
+             FREE (nestret);
+           }
+       }
+      /* Parse an old-style command substitution within double quotes as a
+        single word. */
+      /* XXX - sh and ksh93 don't do this - XXX */
+      else if MBTEST(open == '"' && ch == '`')
+       {
+         nestret = parse_matched_pair (0, '`', '`', &nestlen, rflags);
+add_nestret:
+         if (nestret == &matched_pair_error)
+           {
+             free (ret);
+             return &matched_pair_error;
+           }
+         if (nestlen)
+           {
+             RESIZE_MALLOCED_BUFFER (ret, retind, nestlen, retsize, 64);
+             strcpy (ret + retind, nestret);
+             retind += nestlen;
+           }
+         FREE (nestret);
+       }
+      else if MBTEST(qc == '`' && (ch == '"' || ch == '\'') && in_comment == 0)
+       {
+itrace("parse_matched_pair: parsing quoted string in backquotes");
+         nestret = parse_matched_pair (0, ch, ch, &nestlen, rflags);
+         goto add_nestret;
+       }
+      else if MBTEST(was_dollar && (ch == '(' || ch == '{' || ch == '['))      /* ) } ] */
+       /* check for $(), $[], or ${} inside quoted string. */
+       {
+         if (open == ch)       /* undo previous increment */
+           count--;
+         if (ch == '(')                /* ) */
+           nestret = parse_matched_pair (0, '(', ')', &nestlen, rflags);
+         else if (ch == '{')           /* } */
+           nestret = parse_matched_pair (0, '{', '}', &nestlen, P_FIRSTCLOSE|rflags);
+         else if (ch == '[')           /* ] */
+           nestret = parse_matched_pair (0, '[', ']', &nestlen, rflags);
+
+         goto add_nestret;
+       }
+      was_dollar = MBTEST(ch == '$');
+    }
+
+  ret[retind] = '\0';
+  if (lenp)
+    *lenp = retind;
+  return ret;
+}
+
+#if defined (DPAREN_ARITHMETIC) || defined (ARITH_FOR_COMMAND)
+/* Parse a double-paren construct.  It can be either an arithmetic
+   command, an arithmetic `for' command, or a nested subshell.  Returns
+   the parsed token, -1 on error, or -2 if we didn't do anything and
+   should just go on. */
+static int
+parse_dparen (c)
+     int c;
+{
+  int cmdtyp, len, sline;
+  char *wval, *wv2;
+  WORD_DESC *wd;
+
+#if defined (ARITH_FOR_COMMAND)
+  if (last_read_token == FOR)
+    {
+      arith_for_lineno = line_number;
+      cmdtyp = parse_arith_cmd (&wval, 0);
+      if (cmdtyp == 1)
+       {
+         wd = alloc_word_desc ();
+         wd->word = wval;
+         wd = make_word (wval);
+         yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
+         return (ARITH_FOR_EXPRS);
+       }
+      else
+       return -1;              /* ERROR */
+    }
+#endif
+
+#if defined (DPAREN_ARITHMETIC)
+  if (reserved_word_acceptable (last_read_token))
+    {
+      sline = line_number;
+
+      cmdtyp = parse_arith_cmd (&wval, 0);
+      if (cmdtyp == 1) /* arithmetic command */
+       {
+         wd = alloc_word_desc ();
+         wd->word = wval;
+         wd->flags = W_QUOTED|W_NOSPLIT|W_NOGLOB|W_DQUOTE;
+         yylval.word_list = make_word_list (wd, (WORD_LIST *)NULL);
+         return (ARITH_CMD);
+       }
+      else if (cmdtyp == 0)    /* nested subshell */
+       {
+         push_string (wval, 0, (alias_t *)NULL);
+         if ((parser_state & PST_CASEPAT) == 0)
+           parser_state |= PST_SUBSHELL;
+         return (c);
+       }
+      else                     /* ERROR */
+       return -1;
+    }
+#endif
+
+  return -2;                   /* XXX */
+}
+
+/* We've seen a `(('.  Look for the matching `))'.  If we get it, return 1.
+   If not, assume it's a nested subshell for backwards compatibility and
+   return 0.  In any case, put the characters we've consumed into a locally-
+   allocated buffer and make *ep point to that buffer.  Return -1 on an
+   error, for example EOF. */
+static int
+parse_arith_cmd (ep, adddq)
+     char **ep;
+     int adddq;
+{
+  int exp_lineno, rval, c;
+  char *ttok, *tokstr;
+  int ttoklen;
+
+  exp_lineno = line_number;
+  ttok = parse_matched_pair (0, '(', ')', &ttoklen, 0);
+  rval = 1;
+  if (ttok == &matched_pair_error)
+    return -1;
+  /* Check that the next character is the closing right paren.  If
+     not, this is a syntax error. ( */
+  c = shell_getc (0);
+  if MBTEST(c != ')')
+    rval = 0;
+
+  tokstr = (char *)xmalloc (ttoklen + 4);
+
+  /* if ADDDQ != 0 then (( ... )) -> "..." */
+  if (rval == 1 && adddq)      /* arith cmd, add double quotes */
+    {
+      tokstr[0] = '"';
+      strncpy (tokstr + 1, ttok, ttoklen - 1);
+      tokstr[ttoklen] = '"';
+      tokstr[ttoklen+1] = '\0';
+    }
+  else if (rval == 1)          /* arith cmd, don't add double quotes */
+    {
+      strncpy (tokstr, ttok, ttoklen - 1);
+      tokstr[ttoklen-1] = '\0';
+    }
+  else                         /* nested subshell */
+    {
+      tokstr[0] = '(';
+      strncpy (tokstr + 1, ttok, ttoklen - 1);
+      tokstr[ttoklen] = ')';
+      tokstr[ttoklen+1] = c;
+      tokstr[ttoklen+2] = '\0';
+    }
+
+  *ep = tokstr;
+  FREE (ttok);
+  return rval;
+}
+#endif /* DPAREN_ARITHMETIC || ARITH_FOR_COMMAND */
+
+#if defined (COND_COMMAND)
+static void
+cond_error ()
+{
+  char *etext;
+
+  if (EOF_Reached && cond_token != COND_ERROR)         /* [[ */
+    parser_error (cond_lineno, _("unexpected EOF while looking for `]]'"));
+  else if (cond_token != COND_ERROR)
+    {
+      if (etext = error_token_from_token (cond_token))
+       {
+         parser_error (cond_lineno, _("syntax error in conditional expression: unexpected token `%s'"), etext);
+         free (etext);
+       }
+      else
+       parser_error (cond_lineno, _("syntax error in conditional expression"));
+    }
+}
+
+static COND_COM *
+cond_expr ()
+{
+  return (cond_or ());  
+}
+
+static COND_COM *
+cond_or ()
+{
+  COND_COM *l, *r;
+
+  l = cond_and ();
+  if (cond_token == OR_OR)
+    {
+      r = cond_or ();
+      l = make_cond_node (COND_OR, (WORD_DESC *)NULL, l, r);
+    }
+  return l;
+}
+
+static COND_COM *
+cond_and ()
+{
+  COND_COM *l, *r;
+
+  l = cond_term ();
+  if (cond_token == AND_AND)
+    {
+      r = cond_and ();
+      l = make_cond_node (COND_AND, (WORD_DESC *)NULL, l, r);
+    }
+  return l;
+}
+
+static int
+cond_skip_newlines ()
+{
+  while ((cond_token = read_token (READ)) == '\n')
+    {
+      if (SHOULD_PROMPT ())
+       prompt_again ();
+    }
+  return (cond_token);
+}
+
+#define COND_RETURN_ERROR() \
+  do { cond_token = COND_ERROR; return ((COND_COM *)NULL); } while (0)
+
+static COND_COM *
+cond_term ()
+{
+  WORD_DESC *op;
+  COND_COM *term, *tleft, *tright;
+  int tok, lineno;
+  char *etext;
+
+  /* Read a token.  It can be a left paren, a `!', a unary operator, or a
+     word that should be the first argument of a binary operator.  Start by
+     skipping newlines, since this is a compound command. */
+  tok = cond_skip_newlines ();
+  lineno = line_number;
+  if (tok == COND_END)
+    {
+      COND_RETURN_ERROR ();
+    }
+  else if (tok == '(')
+    {
+      term = cond_expr ();
+      if (cond_token != ')')
+       {
+         if (term)
+           dispose_cond_node (term);           /* ( */
+         if (etext = error_token_from_token (cond_token))
+           {
+             parser_error (lineno, _("unexpected token `%s', expected `)'"), etext);
+             free (etext);
+           }
+         else
+           parser_error (lineno, _("expected `)'"));
+         COND_RETURN_ERROR ();
+       }
+      term = make_cond_node (COND_EXPR, (WORD_DESC *)NULL, term, (COND_COM *)NULL);
+      (void)cond_skip_newlines ();
+    }
+  else if (tok == BANG || (tok == WORD && (yylval.word->word[0] == '!' && yylval.word->word[1] == '\0')))
+    {
+      if (tok == WORD)
+       dispose_word (yylval.word);     /* not needed */
+      term = cond_term ();
+      if (term)
+       term->flags |= CMD_INVERT_RETURN;
+    }
+  else if (tok == WORD && test_unop (yylval.word->word))
+    {
+      op = yylval.word;
+      tok = read_token (READ);
+      if (tok == WORD)
+       {
+         tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+         term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL);
+       }
+      else
+       {
+         dispose_word (op);
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected argument `%s' to conditional unary operator"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("unexpected argument to conditional unary operator"));
+         COND_RETURN_ERROR ();
+       }
+
+      (void)cond_skip_newlines ();
+    }
+  else if (tok == WORD)                /* left argument to binary operator */
+    {
+      /* lhs */
+      tleft = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+
+      /* binop */
+      tok = read_token (READ);
+      if (tok == WORD && test_binop (yylval.word->word))
+       op = yylval.word;
+#if defined (COND_REGEXP)
+      else if (tok == WORD && STREQ (yylval.word->word,"=~"))
+       op = yylval.word;
+#endif
+      else if (tok == '<' || tok == '>')
+       op = make_word_from_token (tok);  /* ( */
+      /* There should be a check before blindly accepting the `)' that we have
+        seen the opening `('. */
+      else if (tok == COND_END || tok == AND_AND || tok == OR_OR || tok == ')')
+       {
+         /* Special case.  [[ x ]] is equivalent to [[ -n x ]], just like
+            the test command.  Similarly for [[ x && expr ]] or
+            [[ x || expr ]] or [[ (x) ]]. */
+         op = make_word ("-n");
+         term = make_cond_node (COND_UNARY, op, tleft, (COND_COM *)NULL);
+         cond_token = tok;
+         return (term);
+       }
+      else
+       {
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected token `%s', conditional binary operator expected"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("conditional binary operator expected"));
+         dispose_cond_node (tleft);
+         COND_RETURN_ERROR ();
+       }
+
+      /* rhs */
+      tok = read_token (READ);
+      if (tok == WORD)
+       {
+         tright = make_cond_node (COND_TERM, yylval.word, (COND_COM *)NULL, (COND_COM *)NULL);
+         term = make_cond_node (COND_BINARY, op, tleft, tright);
+       }
+      else
+       {
+         if (etext = error_token_from_token (tok))
+           {
+             parser_error (line_number, _("unexpected argument `%s' to conditional binary operator"), etext);
+             free (etext);
+           }
+         else
+           parser_error (line_number, _("unexpected argument to conditional binary operator"));
+         dispose_cond_node (tleft);
+         dispose_word (op);
+         COND_RETURN_ERROR ();
+       }
+
+      (void)cond_skip_newlines ();
+    }
+  else
+    {
+      if (tok < 256)
+       parser_error (line_number, _("unexpected token `%c' in conditional command"), tok);
+      else if (etext = error_token_from_token (tok))
+       {
+         parser_error (line_number, _("unexpected token `%s' in conditional command"), etext);
+         free (etext);
+       }
+      else
+       parser_error (line_number, _("unexpected token %d in conditional command"), tok);
+      COND_RETURN_ERROR ();
+    }
+  return (term);
+}      
+
+/* This is kind of bogus -- we slip a mini recursive-descent parser in
+   here to handle the conditional statement syntax. */
+static COMMAND *
+parse_cond_command ()
+{
+  COND_COM *cexp;
+
+  cexp = cond_expr ();
+  return (make_cond_command (cexp));
+}
+#endif
+
+#if defined (ARRAY_VARS)
+/* When this is called, it's guaranteed that we don't care about anything
+   in t beyond i.  We do save and restore the chars, though. */
+static int
+token_is_assignment (t, i)
+     char *t;
+     int i;
+{
+  unsigned char c, c1;
+  int r;
+
+  c = t[i]; c1 = t[i+1];
+  t[i] = '='; t[i+1] = '\0';
+  r = assignment (t, (parser_state & PST_COMPASSIGN) != 0);
+  t[i] = c; t[i+1] = c1;
+  return r;
+}
+
+/* XXX - possible changes here for `+=' */
+static int
+token_is_ident (t, i)
+     char *t;
+     int i;
+{
+  unsigned char c;
+  int r;
+
+  c = t[i];
+  t[i] = '\0';
+  r = legal_identifier (t);
+  t[i] = c;
+  return r;
+}
+#endif
+
+static int
+read_token_word (character)
+     int character;
+{
+  /* The value for YYLVAL when a WORD is read. */
+  WORD_DESC *the_word;
+
+  /* Index into the token that we are building. */
+  int token_index;
+
+  /* ALL_DIGITS becomes zero when we see a non-digit. */
+  int all_digit_token;
+
+  /* DOLLAR_PRESENT becomes non-zero if we see a `$'. */
+  int dollar_present;
+
+  /* COMPOUND_ASSIGNMENT becomes non-zero if we are parsing a compound
+     assignment. */
+  int compound_assignment;
+
+  /* QUOTED becomes non-zero if we see one of ("), ('), (`), or (\). */
+  int quoted;
+
+  /* Non-zero means to ignore the value of the next character, and just
+     to add it no matter what. */
+ int pass_next_character;
+
+  /* The current delimiting character. */
+  int cd;
+  int result, peek_char;
+  char *ttok, *ttrans;
+  int ttoklen, ttranslen;
+  intmax_t lvalue;
+
+  if (token_buffer_size < TOKEN_DEFAULT_INITIAL_SIZE)
+    token = (char *)xrealloc (token, token_buffer_size = TOKEN_DEFAULT_INITIAL_SIZE);
+
+  token_index = 0;
+  all_digit_token = DIGIT (character);
+  dollar_present = quoted = pass_next_character = compound_assignment = 0;
+
+  for (;;)
+    {
+      if (character == EOF)
+       goto got_token;
+
+      if (pass_next_character)
+       {
+         pass_next_character = 0;
+         goto got_character;
+       }
+
+      cd = current_delimiter (dstack);
+
+      /* Handle backslashes.  Quote lots of things when not inside of
+        double-quotes, quote some things inside of double-quotes. */
+      if MBTEST(character == '\\')
+       {
+         peek_char = shell_getc (0);
+
+         /* Backslash-newline is ignored in all cases except
+            when quoted with single quotes. */
+         if (peek_char == '\n')
+           {
+             character = '\n';
+             goto next_character;
+           }
+         else
+           {
+             shell_ungetc (peek_char);
+
+             /* If the next character is to be quoted, note it now. */
+             if (cd == 0 || cd == '`' ||
+                 (cd == '"' && peek_char >= 0 && (sh_syntaxtab[peek_char] & CBSDQUOTE)))
+               pass_next_character++;
+
+             quoted = 1;
+             goto got_character;
+           }
+       }
+
+      /* Parse a matched pair of quote characters. */
+      if MBTEST(shellquote (character))
+       {
+         push_delimiter (dstack, character);
+itrace("read_token_word: calling parse_matched_pair: character = %c", character);
+#if 0
+         ttok = parse_matched_pair (character, character, character, &ttoklen, 0);
+#else
+         ttok = parse_matched_pair (character, character, character, &ttoklen, (character == '`') ? P_COMMAND : 0);
+#endif
+         pop_delimiter (dstack);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size, TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         all_digit_token = 0;
+         quoted = 1;
+         dollar_present |= (character == '"' && strchr (ttok, '$') != 0);
+         FREE (ttok);
+         goto next_character;
+       }
+
+#ifdef EXTENDED_GLOB
+      /* Parse a ksh-style extended pattern matching specification. */
+      if (extended_glob && PATTERN_CHAR (character))
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '(')           /* ) */
+           {
+             push_delimiter (dstack, peek_char);
+             ttok = parse_matched_pair (cd, '(', ')', &ttoklen, 0);
+             pop_delimiter (dstack);
+             if (ttok == &matched_pair_error)
+               return -1;              /* Bail immediately. */
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = character;
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttok);
+             token_index += ttoklen;
+             FREE (ttok);
+             dollar_present = all_digit_token = 0;
+             goto next_character;
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+#endif /* EXTENDED_GLOB */
+
+      /* If the delimiter character is not single quote, parse some of
+        the shell expansions that must be read as a single word. */
+      if (shellexp (character))
+       {
+         peek_char = shell_getc (1);
+         /* $(...), <(...), >(...), $((...)), ${...}, and $[...] constructs */
+         if MBTEST(peek_char == '(' || \
+               ((peek_char == '{' || peek_char == '[') && character == '$'))   /* ) ] } */
+           {
+             if (peek_char == '{')             /* } */
+               ttok = parse_matched_pair (cd, '{', '}', &ttoklen, P_FIRSTCLOSE);
+             else if (peek_char == '(')                /* ) */
+               {
+                 /* XXX - push and pop the `(' as a delimiter for use by
+                    the command-oriented-history code.  This way newlines
+                    appearing in the $(...) string get added to the
+                    history literally rather than causing a possibly-
+                    incorrect `;' to be added. ) */
+                 push_delimiter (dstack, peek_char);
+                 ttok = parse_matched_pair (cd, '(', ')', &ttoklen, P_COMMAND);
+                 pop_delimiter (dstack);
+               }
+             else
+               ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+             if (ttok == &matched_pair_error)
+               return -1;              /* Bail immediately. */
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             token[token_index++] = character;
+             token[token_index++] = peek_char;
+             strcpy (token + token_index, ttok);
+             token_index += ttoklen;
+             FREE (ttok);
+             dollar_present = 1;
+             all_digit_token = 0;
+             goto next_character;
+           }
+         /* This handles $'...' and $"..." new-style quoted strings. */
+         else if MBTEST(character == '$' && (peek_char == '\'' || peek_char == '"'))
+           {
+             int first_line;
+
+             first_line = line_number;
+             push_delimiter (dstack, peek_char);
+             ttok = parse_matched_pair (peek_char, peek_char, peek_char,
+                                        &ttoklen,
+                                        (peek_char == '\'') ? P_ALLOWESC : 0);
+             pop_delimiter (dstack);
+             if (ttok == &matched_pair_error)
+               return -1;
+             if (peek_char == '\'')
+               {
+                 ttrans = ansiexpand (ttok, 0, ttoklen - 1, &ttranslen);
+                 free (ttok);
+
+                 /* Insert the single quotes and correctly quote any
+                    embedded single quotes (allowed because P_ALLOWESC was
+                    passed to parse_matched_pair). */
+                 ttok = sh_single_quote (ttrans);
+                 free (ttrans);
+                 ttranslen = strlen (ttok);
+                 ttrans = ttok;
+               }
+             else
+               {
+                 /* Try to locale)-expand the converted string. */
+                 ttrans = localeexpand (ttok, 0, ttoklen - 1, first_line, &ttranslen);
+                 free (ttok);
+
+                 /* Add the double quotes back */
+                 ttok = sh_mkdoublequoted (ttrans, ttranslen, 0);
+                 free (ttrans);
+                 ttranslen += 2;
+                 ttrans = ttok;
+               }
+
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttranslen + 2,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             strcpy (token + token_index, ttrans);
+             token_index += ttranslen;
+             FREE (ttrans);
+             quoted = 1;
+             all_digit_token = 0;
+             goto next_character;
+           }
+         /* This could eventually be extended to recognize all of the
+            shell's single-character parameter expansions, and set flags.*/
+         else if MBTEST(character == '$' && peek_char == '$')
+           {
+             ttok = (char *)xmalloc (3);
+             ttok[0] = ttok[1] = '$';
+             ttok[2] = '\0';
+             RESIZE_MALLOCED_BUFFER (token, token_index, 3,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+             strcpy (token + token_index, ttok);
+             token_index += 2;
+             dollar_present = 1;
+             all_digit_token = 0;
+             FREE (ttok);
+             goto next_character;
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+
+#if defined (ARRAY_VARS)
+      /* Identify possible array subscript assignment; match [...] */
+      else if MBTEST(character == '[' && token_index > 0 && assignment_acceptable (last_read_token) && token_is_ident (token, token_index))    /* ] */
+        {
+         ttok = parse_matched_pair (cd, '[', ']', &ttoklen, 0);
+         if (ttok == &matched_pair_error)
+           return -1;          /* Bail immediately. */
+         RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 2,
+                                 token_buffer_size,
+                                 TOKEN_DEFAULT_GROW_SIZE);
+         token[token_index++] = character;
+         strcpy (token + token_index, ttok);
+         token_index += ttoklen;
+         FREE (ttok);
+         all_digit_token = 0;
+         goto next_character;
+        }
+      /* Identify possible compound array variable assignment. */
+      else if MBTEST(character == '=' && token_index > 0 && (assignment_acceptable (last_read_token) || (parser_state & PST_ASSIGNOK)) && token_is_assignment (token, token_index))
+       {
+         peek_char = shell_getc (1);
+         if MBTEST(peek_char == '(')           /* ) */
+           {
+             ttok = parse_compound_assignment (&ttoklen);
+
+             RESIZE_MALLOCED_BUFFER (token, token_index, ttoklen + 4,
+                                     token_buffer_size,
+                                     TOKEN_DEFAULT_GROW_SIZE);
+
+             token[token_index++] = '=';
+             token[token_index++] = '(';
+             if (ttok)
+               {
+                 strcpy (token + token_index, ttok);
+                 token_index += ttoklen;
+               }
+             token[token_index++] = ')';
+             FREE (ttok);
+             all_digit_token = 0;
+             compound_assignment = 1;
+#if 0
+             goto next_character;
+#else
+             goto got_token;           /* ksh93 seems to do this */
+#endif
+           }
+         else
+           shell_ungetc (peek_char);
+       }
+#endif
+
+      /* When not parsing a multi-character word construct, shell meta-
+        characters break words. */
+      if MBTEST(shellbreak (character))
+       {
+         shell_ungetc (character);
+         goto got_token;
+       }
+
+    got_character:
+
+      all_digit_token &= DIGIT (character);
+      dollar_present |= character == '$';
+
+      if (character == CTLESC || character == CTLNUL)
+       token[token_index++] = CTLESC;
+
+      token[token_index++] = character;
+
+      RESIZE_MALLOCED_BUFFER (token, token_index, 1, token_buffer_size,
+                             TOKEN_DEFAULT_GROW_SIZE);
+
+    next_character:
+      if (character == '\n' && SHOULD_PROMPT ())
+       prompt_again ();
+
+      /* We want to remove quoted newlines (that is, a \<newline> pair)
+        unless we are within single quotes or pass_next_character is
+        set (the shell equivalent of literal-next). */
+      cd = current_delimiter (dstack);
+      character = shell_getc (cd != '\'' && pass_next_character == 0);
+    }  /* end for (;;) */
+
+got_token:
+
+  token[token_index] = '\0';
+
+  /* Check to see what thing we should return.  If the last_read_token
+     is a `<', or a `&', or the character which ended this token is
+     a '>' or '<', then, and ONLY then, is this input token a NUMBER.
+     Otherwise, it is just a word, and should be returned as such. */
+  if MBTEST(all_digit_token && (character == '<' || character == '>' || \
+                   last_read_token == LESS_AND || \
+                   last_read_token == GREATER_AND))
+      {
+       if (legal_number (token, &lvalue) && (int)lvalue == lvalue)
+         yylval.number = lvalue;
+       else
+         yylval.number = -1;
+       return (NUMBER);
+      }
+
+  /* Check for special case tokens. */
+  result = (last_shell_getc_is_singlebyte) ? special_case_tokens (token) : -1;
+  if (result >= 0)
+    return result;
+
+#if defined (ALIAS)
+  /* Posix.2 does not allow reserved words to be aliased, so check for all
+     of them, including special cases, before expanding the current token
+     as an alias. */
+  if MBTEST(posixly_correct)
+    CHECK_FOR_RESERVED_WORD (token);
+
+  /* Aliases are expanded iff EXPAND_ALIASES is non-zero, and quoting
+     inhibits alias expansion. */
+  if (expand_aliases && quoted == 0)
+    {
+      result = alias_expand_token (token);
+      if (result == RE_READ_TOKEN)
+       return (RE_READ_TOKEN);
+      else if (result == NO_EXPANSION)
+       parser_state &= ~PST_ALEXPNEXT;
+    }
+
+  /* If not in Posix.2 mode, check for reserved words after alias
+     expansion. */
+  if MBTEST(posixly_correct == 0)
+#endif
+    CHECK_FOR_RESERVED_WORD (token);
+
+  the_word = (WORD_DESC *)xmalloc (sizeof (WORD_DESC));
+  the_word->word = (char *)xmalloc (1 + token_index);
+  the_word->flags = 0;
+  strcpy (the_word->word, token);
+  if (dollar_present)
+    the_word->flags |= W_HASDOLLAR;
+  if (quoted)
+    the_word->flags |= W_QUOTED;
+  if (compound_assignment)
+    the_word->flags |= W_COMPASSIGN;
+  /* A word is an assignment if it appears at the beginning of a
+     simple command, or after another assignment word.  This is
+     context-dependent, so it cannot be handled in the grammar. */
+  if (assignment (token, (parser_state & PST_COMPASSIGN) != 0))
+    {
+      the_word->flags |= W_ASSIGNMENT;
+      /* Don't perform word splitting on assignment statements. */
+      if (assignment_acceptable (last_read_token) || (parser_state & PST_COMPASSIGN) != 0)
+       the_word->flags |= W_NOSPLIT;
+    }
+
+  if (command_token_position (last_read_token))
+    {
+      struct builtin *b;
+      b = builtin_address_internal (token, 0);
+      if (b && (b->flags & ASSIGNMENT_BUILTIN))
+        parser_state |= PST_ASSIGNOK;
+    }
+
+  yylval.word = the_word;
+
+  result = ((the_word->flags & (W_ASSIGNMENT|W_NOSPLIT)) == (W_ASSIGNMENT|W_NOSPLIT))
+               ? ASSIGNMENT_WORD : WORD;
+
+  switch (last_read_token)
+    {
+    case FUNCTION:
+      parser_state |= PST_ALLOWOPNBRC;
+      function_dstart = line_number;
+      break;
+    case CASE:
+    case SELECT:
+    case FOR:
+      if (word_top < MAX_CASE_NEST)
+       word_top++;
+      word_lineno[word_top] = line_number;
+      break;
+    }
+
+  return (result);
+}
+
+/* Return 1 if TOKSYM is a token that after being read would allow
+   a reserved word to be seen, else 0. */
+static int
+reserved_word_acceptable (toksym)
+     int toksym;
+{
+  switch (toksym)
+    {
+    case '\n':
+    case ';':
+    case '(':
+    case ')':
+    case '|':
+    case '&':
+    case '{':
+    case '}':          /* XXX */
+    case AND_AND:
+    case BANG:
+    case DO:
+    case DONE:
+    case ELIF:
+    case ELSE:
+    case ESAC:
+    case FI:
+    case IF:
+    case OR_OR:
+    case SEMI_SEMI:
+    case THEN:
+    case TIME:
+    case TIMEOPT:
+    case UNTIL:
+    case WHILE:
+    case 0:
+      return 1;
+    default:
+      return 0;
+    }
+}
+    
+/* Return the index of TOKEN in the alist of reserved words, or -1 if
+   TOKEN is not a shell reserved word. */
+int
+find_reserved_word (tokstr)
+     char *tokstr;
+{
+  int i;
+  for (i = 0; word_token_alist[i].word; i++)
+    if (STREQ (tokstr, word_token_alist[i].word))
+      return i;
+  return -1;
+}
+
+#if 0
+#if defined (READLINE)
+/* Called after each time readline is called.  This insures that whatever
+   the new prompt string is gets propagated to readline's local prompt
+   variable. */
+static void
+reset_readline_prompt ()
+{
+  char *temp_prompt;
+
+  if (prompt_string_pointer)
+    {
+      temp_prompt = (*prompt_string_pointer)
+                       ? decode_prompt_string (*prompt_string_pointer)
+                       : (char *)NULL;
+
+      if (temp_prompt == 0)
+       {
+         temp_prompt = (char *)xmalloc (1);
+         temp_prompt[0] = '\0';
+       }
+
+      FREE (current_readline_prompt);
+      current_readline_prompt = temp_prompt;
+    }
+}
+#endif /* READLINE */
+#endif /* 0 */
+
+#if defined (HISTORY)
+/* A list of tokens which can be followed by newlines, but not by
+   semi-colons.  When concatenating multiple lines of history, the
+   newline separator for such tokens is replaced with a space. */
+static int no_semi_successors[] = {
+  '\n', '{', '(', ')', ';', '&', '|',
+  CASE, DO, ELSE, IF, SEMI_SEMI, THEN, UNTIL, WHILE, AND_AND, OR_OR, IN,
+  0
+};
+
+/* If we are not within a delimited expression, try to be smart
+   about which separators can be semi-colons and which must be
+   newlines.  Returns the string that should be added into the
+   history entry. */
+char *
+history_delimiting_chars ()
+{
+  register int i;
+
+  if (dstack.delimiter_depth != 0)
+    return ("\n");
+    
+  /* First, handle some special cases. */
+  /*(*/
+  /* If we just read `()', assume it's a function definition, and don't
+     add a semicolon.  If the token before the `)' was not `(', and we're
+     not in the midst of parsing a case statement, assume it's a
+     parenthesized command and add the semicolon. */
+  /*)(*/
+  if (token_before_that == ')')
+    {
+      if (two_tokens_ago == '(')       /*)*/   /* function def */
+       return " ";
+      /* This does not work for subshells inside case statement
+        command lists.  It's a suboptimal solution. */
+      else if (parser_state & PST_CASESTMT)    /* case statement pattern */
+       return " ";
+      else     
+       return "; ";                            /* (...) subshell */
+    }
+  else if (token_before_that == WORD && two_tokens_ago == FUNCTION)
+    return " ";                /* function def using `function name' without `()' */
+
+  else if (token_before_that == WORD && two_tokens_ago == FOR)
+    {
+      /* Tricky.  `for i\nin ...' should not have a semicolon, but
+        `for i\ndo ...' should.  We do what we can. */
+      for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++)
+       ;
+      if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
+       return " ";
+      return ";";
+    }
+  else if (two_tokens_ago == CASE && token_before_that == WORD && (parser_state & PST_CASESTMT))
+    return " ";
+
+  for (i = 0; no_semi_successors[i]; i++)
+    {
+      if (token_before_that == no_semi_successors[i])
+       return (" ");
+    }
+
+  return ("; ");
+}
+#endif /* HISTORY */
+
+/* Issue a prompt, or prepare to issue a prompt when the next character
+   is read. */
+static void
+prompt_again ()
+{
+  char *temp_prompt;
+
+  if (interactive == 0 || expanding_alias())   /* XXX */
+    return;
+
+  ps1_prompt = get_string_value ("PS1");
+  ps2_prompt = get_string_value ("PS2");
+
+  if (!prompt_string_pointer)
+    prompt_string_pointer = &ps1_prompt;
+
+  temp_prompt = *prompt_string_pointer
+                       ? decode_prompt_string (*prompt_string_pointer)
+                       : (char *)NULL;
+
+  if (temp_prompt == 0)
+    {
+      temp_prompt = (char *)xmalloc (1);
+      temp_prompt[0] = '\0';
+    }
+
+  current_prompt_string = *prompt_string_pointer;
+  prompt_string_pointer = &ps2_prompt;
+
+#if defined (READLINE)
+  if (!no_line_editing)
+    {
+      FREE (current_readline_prompt);
+      current_readline_prompt = temp_prompt;
+    }
+  else
+#endif /* READLINE */
+    {
+      FREE (current_decoded_prompt);
+      current_decoded_prompt = temp_prompt;
+    }
+}
+
+int
+get_current_prompt_level ()
+{
+  return ((current_prompt_string && current_prompt_string == ps2_prompt) ? 2 : 1);
+}
+
+void
+set_current_prompt_level (x)
+     int x;
+{
+  prompt_string_pointer = (x == 2) ? &ps2_prompt : &ps1_prompt;
+  current_prompt_string = *prompt_string_pointer;
+}
+      
+static void
+print_prompt ()
+{
+  fprintf (stderr, "%s", current_decoded_prompt);
+  fflush (stderr);
+}
+
+/* Return a string which will be printed as a prompt.  The string
+   may contain special characters which are decoded as follows:
+
+       \a      bell (ascii 07)
+       \d      the date in Day Mon Date format
+       \e      escape (ascii 033)
+       \h      the hostname up to the first `.'
+       \H      the hostname
+       \j      the number of active jobs
+       \l      the basename of the shell's tty device name
+       \n      CRLF
+       \r      CR
+       \s      the name of the shell
+       \t      the time in 24-hour hh:mm:ss format
+       \T      the time in 12-hour hh:mm:ss format
+       \@      the time in 12-hour hh:mm am/pm format
+       \A      the time in 24-hour hh:mm format
+       \D{fmt} the result of passing FMT to strftime(3)
+       \u      your username
+       \v      the version of bash (e.g., 2.00)
+       \V      the release of bash, version + patchlevel (e.g., 2.00.0)
+       \w      the current working directory
+       \W      the last element of $PWD
+       \!      the history number of this command
+       \#      the command number of this command
+       \$      a $ or a # if you are root
+       \nnn    character code nnn in octal
+       \\      a backslash
+       \[      begin a sequence of non-printing chars
+       \]      end a sequence of non-printing chars
+*/
+#define PROMPT_GROWTH 48
+char *
+decode_prompt_string (string)
+     char *string;
+{
+  WORD_LIST *list;
+  char *result, *t;
+  struct dstack save_dstack;
+  int last_exit_value;
+#if defined (PROMPT_STRING_DECODE)
+  int result_size, result_index;
+  int c, n;
+  char *temp, octal_string[4];
+  struct tm *tm;  
+  time_t the_time;
+  char timebuf[128];
+  char *timefmt;
+
+  result = (char *)xmalloc (result_size = PROMPT_GROWTH);
+  result[result_index = 0] = 0;
+  temp = (char *)NULL;
+
+  while (c = *string++)
+    {
+      if (posixly_correct && c == '!')
+       {
+         if (*string == '!')
+           {
+             temp = savestring ("!");
+             goto add_string;
+           }
+         else
+           {
+#if !defined (HISTORY)
+               temp = savestring ("1");
+#else /* HISTORY */
+               temp = itos (history_number ());
+#endif /* HISTORY */
+               string--;       /* add_string increments string again. */
+               goto add_string;
+           }
+       }
+      if (c == '\\')
+       {
+         c = *string;
+
+         switch (c)
+           {
+           case '0':
+           case '1':
+           case '2':
+           case '3':
+           case '4':
+           case '5':
+           case '6':
+           case '7':
+             strncpy (octal_string, string, 3);
+             octal_string[3] = '\0';
+
+             n = read_octal (octal_string);
+             temp = (char *)xmalloc (3);
+
+             if (n == CTLESC || n == CTLNUL)
+               {
+                 temp[0] = CTLESC;
+                 temp[1] = n;
+                 temp[2] = '\0';
+               }
+             else if (n == -1)
+               {
+                 temp[0] = '\\';
+                 temp[1] = '\0';
+               }
+             else
+               {
+                 temp[0] = n;
+                 temp[1] = '\0';
+               }
+
+             for (c = 0; n != -1 && c < 3 && ISOCTAL (*string); c++)
+               string++;
+
+             c = 0;            /* tested at add_string: */
+             goto add_string;
+
+           case 'd':
+           case 't':
+           case 'T':
+           case '@':
+           case 'A':
+             /* Make the current time/date into a string. */
+             (void) time (&the_time);
+             tm = localtime (&the_time);
+
+             if (c == 'd')
+               n = strftime (timebuf, sizeof (timebuf), "%a %b %d", tm);
+             else if (c == 't')
+               n = strftime (timebuf, sizeof (timebuf), "%H:%M:%S", tm);
+             else if (c == 'T')
+               n = strftime (timebuf, sizeof (timebuf), "%I:%M:%S", tm);
+             else if (c == '@')
+               n = strftime (timebuf, sizeof (timebuf), "%I:%M %p", tm);
+             else if (c == 'A')
+               n = strftime (timebuf, sizeof (timebuf), "%H:%M", tm);
+
+             if (n == 0)
+               timebuf[0] = '\0';
+             else
+               timebuf[sizeof(timebuf) - 1] = '\0';
+
+             temp = savestring (timebuf);
+             goto add_string;
+
+           case 'D':           /* strftime format */
+             if (string[1] != '{')             /* } */
+               goto not_escape;
+
+             (void) time (&the_time);
+             tm = localtime (&the_time);
+             string += 2;                      /* skip { */
+             timefmt = xmalloc (strlen (string) + 3);
+             for (t = timefmt; *string && *string != '}'; )
+               *t++ = *string++;
+             *t = '\0';
+             c = *string;      /* tested at add_string */
+             if (timefmt[0] == '\0')
+               {
+                 timefmt[0] = '%';
+                 timefmt[1] = 'X';     /* locale-specific current time */
+                 timefmt[2] = '\0';
+               }
+             n = strftime (timebuf, sizeof (timebuf), timefmt, tm);
+             free (timefmt);
+
+             if (n == 0)
+               timebuf[0] = '\0';
+             else
+               timebuf[sizeof(timebuf) - 1] = '\0';
+
+             if (promptvars || posixly_correct)
+               /* Make sure that expand_prompt_string is called with a
+                  second argument of Q_DOUBLE_QUOTES if we use this
+                  function here. */
+               temp = sh_backslash_quote_for_double_quotes (timebuf);
+             else
+               temp = savestring (timebuf);
+             goto add_string;
+             
+           case 'n':
+             temp = (char *)xmalloc (3);
+             temp[0] = no_line_editing ? '\n' : '\r';
+             temp[1] = no_line_editing ? '\0' : '\n';
+             temp[2] = '\0';
+             goto add_string;
+
+           case 's':
+             temp = base_pathname (shell_name);
+             temp = savestring (temp);
+             goto add_string;
+
+           case 'v':
+           case 'V':
+             temp = (char *)xmalloc (16);
+             if (c == 'v')
+               strcpy (temp, dist_version);
+             else
+               sprintf (temp, "%s.%d", dist_version, patch_level);
+             goto add_string;
+
+           case 'w':
+           case 'W':
+             {
+               /* Use the value of PWD because it is much more efficient. */
+               char t_string[PATH_MAX], *t;
+               int tlen;
+
+               temp = get_string_value ("PWD");
+
+               if (temp == 0)
+                 {
+                   if (getcwd (t_string, sizeof(t_string)) == 0)
+                     {
+                       t_string[0] = '.';
+                       tlen = 1;
+                     }
+                   else
+                     tlen = strlen (t_string);
+                 }
+               else
+                 {
+                   tlen = sizeof (t_string) - 1;
+                   strncpy (t_string, temp, tlen);
+                 }
+               t_string[tlen] = '\0';
+
+#define ROOT_PATH(x)   ((x)[0] == '/' && (x)[1] == 0)
+#define DOUBLE_SLASH_ROOT(x)   ((x)[0] == '/' && (x)[1] == '/' && (x)[2] == 0)
+               /* Abbreviate \W as ~ if $PWD == $HOME */
+               if (c == 'W' && (((t = get_string_value ("HOME")) == 0) || STREQ (t, t_string) == 0))
+                 {
+                   if (ROOT_PATH (t_string) == 0 && DOUBLE_SLASH_ROOT (t_string) == 0)
+                     {
+                       t = strrchr (t_string, '/');
+                       if (t)
+                         strcpy (t_string, t + 1);
+                     }
+                 }
+#undef ROOT_PATH
+#undef DOUBLE_SLASH_ROOT
+               else
+                 /* polite_directory_format is guaranteed to return a string
+                    no longer than PATH_MAX - 1 characters. */
+                 strcpy (t_string, polite_directory_format (t_string));
+
+               /* If we're going to be expanding the prompt string later,
+                  quote the directory name. */
+               if (promptvars || posixly_correct)
+                 /* Make sure that expand_prompt_string is called with a
+                    second argument of Q_DOUBLE_QUOTES if we use this
+                    function here. */
+                 temp = sh_backslash_quote_for_double_quotes (t_string);
+               else
+                 temp = savestring (t_string);
+
+               goto add_string;
+             }
+
+           case 'u':
+             if (current_user.user_name == 0)
+               get_current_user_info ();
+             temp = savestring (current_user.user_name);
+             goto add_string;
+
+           case 'h':
+           case 'H':
+             temp = savestring (current_host_name);
+             if (c == 'h' && (t = (char *)strchr (temp, '.')))
+               *t = '\0';
+             goto add_string;
+
+           case '#':
+             temp = itos (current_command_number);
+             goto add_string;
+
+           case '!':
+#if !defined (HISTORY)
+             temp = savestring ("1");
+#else /* HISTORY */
+             temp = itos (history_number ());
+#endif /* HISTORY */
+             goto add_string;
+
+           case '$':
+             t = temp = (char *)xmalloc (3);
+             if ((promptvars || posixly_correct) && (current_user.euid != 0))
+               *t++ = '\\';
+             *t++ = current_user.euid == 0 ? '#' : '$';
+             *t = '\0';
+             goto add_string;
+
+           case 'j':
+             temp = itos (count_all_jobs ());
+             goto add_string;
+
+           case 'l':
+#if defined (HAVE_TTYNAME)
+             temp = (char *)ttyname (fileno (stdin));
+             t = temp ? base_pathname (temp) : "tty";
+             temp = savestring (t);
+#else
+             temp = savestring ("tty");
+#endif /* !HAVE_TTYNAME */
+             goto add_string;
+
+#if defined (READLINE)
+           case '[':
+           case ']':
+             if (no_line_editing)
+               {
+                 string++;
+                 break;
+               }
+             temp = (char *)xmalloc (3);
+             temp[0] = '\001';
+             temp[1] = (c == '[') ? RL_PROMPT_START_IGNORE : RL_PROMPT_END_IGNORE;
+             temp[2] = '\0';
+             goto add_string;
+#endif /* READLINE */
+
+           case '\\':
+           case 'a':
+           case 'e':
+           case 'r':
+             temp = (char *)xmalloc (2);
+             if (c == 'a')
+               temp[0] = '\07';
+             else if (c == 'e')
+               temp[0] = '\033';
+             else if (c == 'r')
+               temp[0] = '\r';
+             else                      /* (c == '\\') */
+               temp[0] = c;
+             temp[1] = '\0';
+             goto add_string;
+
+           default:
+not_escape:
+             temp = (char *)xmalloc (3);
+             temp[0] = '\\';
+             temp[1] = c;
+             temp[2] = '\0';
+
+           add_string:
+             if (c)
+               string++;
+             result =
+               sub_append_string (temp, result, &result_index, &result_size);
+             temp = (char *)NULL; /* Freed in sub_append_string (). */
+             result[result_index] = '\0';
+             break;
+           }
+       }
+      else
+       {
+         RESIZE_MALLOCED_BUFFER (result, result_index, 3, result_size, PROMPT_GROWTH);
+         result[result_index++] = c;
+         result[result_index] = '\0';
+       }
+    }
+#else /* !PROMPT_STRING_DECODE */
+  result = savestring (string);
+#endif /* !PROMPT_STRING_DECODE */
+
+  /* Save the delimiter stack and point `dstack' to temp space so any
+     command substitutions in the prompt string won't result in screwing
+     up the parser's quoting state. */
+  save_dstack = dstack;
+  dstack = temp_dstack;
+  dstack.delimiter_depth = 0;
+
+  /* Perform variable and parameter expansion and command substitution on
+     the prompt string. */
+  if (promptvars || posixly_correct)
+    {
+      last_exit_value = last_command_exit_value;
+      list = expand_prompt_string (result, Q_DOUBLE_QUOTES);
+      free (result);
+      result = string_list (list);
+      dispose_words (list);
+      last_command_exit_value = last_exit_value;
+    }
+  else
+    {
+      t = dequote_string (result);
+      free (result);
+      result = t;
+    }
+
+  dstack = save_dstack;
+
+  return (result);
+}
+
+/************************************************
+ *                                             *
+ *             ERROR HANDLING                  *
+ *                                             *
+ ************************************************/
+
+/* Report a syntax error, and restart the parser.  Call here for fatal
+   errors. */
+int
+yyerror (msg)
+     const char *msg;
+{
+  report_syntax_error ((char *)NULL);
+  reset_parser ();
+  return (0);
+}
+
+static char *
+error_token_from_token (token)
+     int token;
+{
+  char *t;
+
+  if (t = find_token_in_alist (token, word_token_alist, 0))
+    return t;
+
+  if (t = find_token_in_alist (token, other_token_alist, 0))
+    return t;
+
+  t = (char *)NULL;
+  /* This stuff is dicy and needs closer inspection */
+  switch (current_token)
+    {
+    case WORD:
+    case ASSIGNMENT_WORD:
+      if (yylval.word)
+       t = savestring (yylval.word->word);
+      break;
+    case NUMBER:
+      t = itos (yylval.number);
+      break;
+    case ARITH_CMD:
+      if (yylval.word_list)
+        t = string_list (yylval.word_list);
+      break;
+    case ARITH_FOR_EXPRS:
+      if (yylval.word_list)
+       t = string_list_internal (yylval.word_list, " ; ");
+      break;
+    case COND_CMD:
+      t = (char *)NULL;                /* punt */
+      break;
+    }
+
+  return t;
+}
+
+static char *
+error_token_from_text ()
+{
+  char *msg, *t;
+  int token_end, i;
+
+  t = shell_input_line;
+  i = shell_input_line_index;
+  token_end = 0;
+  msg = (char *)NULL;
+
+  if (i && t[i] == '\0')
+    i--;
+
+  while (i && (whitespace (t[i]) || t[i] == '\n'))
+    i--;
+
+  if (i)
+    token_end = i + 1;
+
+  while (i && (member (t[i], " \n\t;|&") == 0))
+    i--;
+
+  while (i != token_end && (whitespace (t[i]) || t[i] == '\n'))
+    i++;
+
+  /* Return our idea of the offending token. */
+  if (token_end || (i == 0 && token_end == 0))
+    {
+      if (token_end)
+       msg = substring (t, i, token_end);
+      else     /* one-character token */
+       {
+         msg = (char *)xmalloc (2);
+         msg[0] = t[i];
+         msg[1] = '\0';
+       }
+    }
+
+  return (msg);
+}
+
+static void
+print_offending_line ()
+{
+  char *msg;
+  int token_end;
+
+  msg = savestring (shell_input_line);
+  token_end = strlen (msg);
+  while (token_end && msg[token_end - 1] == '\n')
+    msg[--token_end] = '\0';
+
+  parser_error (line_number, "`%s'", msg);
+  free (msg);
+}
+
+/* Report a syntax error with line numbers, etc.
+   Call here for recoverable errors.  If you have a message to print,
+   then place it in MESSAGE, otherwise pass NULL and this will figure
+   out an appropriate message for you. */
+static void
+report_syntax_error (message)
+     char *message;
+{
+  char *msg;
+
+  if (message)
+    {
+      parser_error (line_number, "%s", message);
+      if (interactive && EOF_Reached)
+       EOF_Reached = 0;
+      last_command_exit_value = EX_USAGE;
+      return;
+    }
+
+  /* If the line of input we're reading is not null, try to find the
+     objectionable token.  First, try to figure out what token the
+     parser's complaining about by looking at current_token. */
+  if (current_token != 0 && EOF_Reached == 0 && (msg = error_token_from_token (current_token)))
+    {
+      parser_error (line_number, _("syntax error near unexpected token `%s'"), msg);
+      free (msg);
+
+      if (interactive == 0)
+       print_offending_line ();
+
+      last_command_exit_value = EX_USAGE;
+      return;
+    }
+
+  /* If looking at the current token doesn't prove fruitful, try to find the
+     offending token by analyzing the text of the input line near the current
+     input line index and report what we find. */
+  if (shell_input_line && *shell_input_line)
+    {
+      msg = error_token_from_text ();
+      if (msg)
+       {
+         parser_error (line_number, _("syntax error near `%s'"), msg);
+         free (msg);
+       }
+
+      /* If not interactive, print the line containing the error. */
+      if (interactive == 0)
+        print_offending_line ();
+    }
+  else
+    {
+      msg = EOF_Reached ? _("syntax error: unexpected end of file") : _("syntax error");
+      parser_error (line_number, "%s", msg);
+      /* When the shell is interactive, this file uses EOF_Reached
+        only for error reporting.  Other mechanisms are used to
+        decide whether or not to exit. */
+      if (interactive && EOF_Reached)
+       EOF_Reached = 0;
+    }
+
+  last_command_exit_value = EX_USAGE;
+}
+
+/* ??? Needed function. ??? We have to be able to discard the constructs
+   created during parsing.  In the case of error, we want to return
+   allocated objects to the memory pool.  In the case of no error, we want
+   to throw away the information about where the allocated objects live.
+   (dispose_command () will actually free the command.) */
+static void
+discard_parser_constructs (error_p)
+     int error_p;
+{
+}
+
+/************************************************
+ *                                             *
+ *             EOF HANDLING                    *
+ *                                             *
+ ************************************************/
+
+/* Do that silly `type "bye" to exit' stuff.  You know, "ignoreeof". */
+
+/* A flag denoting whether or not ignoreeof is set. */
+int ignoreeof = 0;
+
+/* The number of times that we have encountered an EOF character without
+   another character intervening.  When this gets above the limit, the
+   shell terminates. */
+int eof_encountered = 0;
+
+/* The limit for eof_encountered. */
+int eof_encountered_limit = 10;
+
+/* If we have EOF as the only input unit, this user wants to leave
+   the shell.  If the shell is not interactive, then just leave.
+   Otherwise, if ignoreeof is set, and we haven't done this the
+   required number of times in a row, print a message. */
+static void
+handle_eof_input_unit ()
+{
+  if (interactive)
+    {
+      /* shell.c may use this to decide whether or not to write out the
+        history, among other things.  We use it only for error reporting
+        in this file. */
+      if (EOF_Reached)
+       EOF_Reached = 0;
+
+      /* If the user wants to "ignore" eof, then let her do so, kind of. */
+      if (ignoreeof)
+       {
+         if (eof_encountered < eof_encountered_limit)
+           {
+             fprintf (stderr, _("Use \"%s\" to leave the shell.\n"),
+                      login_shell ? "logout" : "exit");
+             eof_encountered++;
+             /* Reset the parsing state. */
+             last_read_token = current_token = '\n';
+             /* Reset the prompt string to be $PS1. */
+             prompt_string_pointer = (char **)NULL;
+             prompt_again ();
+             return;
+           }
+       }
+
+      /* In this case EOF should exit the shell.  Do it now. */
+      reset_parser ();
+      exit_builtin ((WORD_LIST *)NULL);
+    }
+  else
+    {
+      /* We don't write history files, etc., for non-interactive shells. */
+      EOF_Reached = 1;
+    }
+}
+
+/************************************************
+ *                                             *
+ *     STRING PARSING FUNCTIONS                *
+ *                                             *
+ ************************************************/
+
+/* It's very important that these two functions treat the characters
+   between ( and ) identically. */
+
+static WORD_LIST parse_string_error;
+
+/* Take a string and run it through the shell parser, returning the
+   resultant word list.  Used by compound array assignment. */
+WORD_LIST *
+parse_string_to_word_list (s, flags, whom)
+     char *s;
+     int flags;
+     const char *whom;
+{
+  WORD_LIST *wl;
+  int tok, orig_current_token, orig_line_number, orig_input_terminator;
+  int orig_line_count;
+  int old_echo_input, old_expand_aliases;
+#if defined (HISTORY)
+  int old_remember_on_history, old_history_expansion_inhibited;
+#endif
+
+#if defined (HISTORY)
+  old_remember_on_history = remember_on_history;
+#  if defined (BANG_HISTORY)
+  old_history_expansion_inhibited = history_expansion_inhibited;
+#  endif
+  bash_history_disable ();
+#endif
+
+  orig_line_number = line_number;
+  orig_line_count = current_command_line_count;
+  orig_input_terminator = shell_input_line_terminator;
+  old_echo_input = echo_input_at_read;
+  old_expand_aliases = expand_aliases;
+
+  push_stream (1);
+  last_read_token = WORD;              /* WORD to allow reserved words here */
+  current_command_line_count = 0;
+  echo_input_at_read = expand_aliases = 0;
+
+  with_input_from_string (s, whom);
+  wl = (WORD_LIST *)NULL;
+
+  if (flags & 1)
+    parser_state |= PST_COMPASSIGN;
+
+  while ((tok = read_token (READ)) != yacc_EOF)
+    {
+      if (tok == '\n' && *bash_input.location.string == '\0')
+       break;
+      if (tok == '\n')         /* Allow newlines in compound assignments */
+       continue;
+      if (tok != WORD && tok != ASSIGNMENT_WORD)
+       {
+         line_number = orig_line_number + line_number - 1;
+         orig_current_token = current_token;
+         current_token = tok;
+         yyerror (NULL);       /* does the right thing */
+         current_token = orig_current_token;
+         if (wl)
+           dispose_words (wl);
+         wl = &parse_string_error;
+         break;
+       }
+      wl = make_word_list (yylval.word, wl);
+    }
+  
+  last_read_token = '\n';
+  pop_stream ();
+
+#if defined (HISTORY)
+  remember_on_history = old_remember_on_history;
+#  if defined (BANG_HISTORY)
+  history_expansion_inhibited = old_history_expansion_inhibited;
+#  endif /* BANG_HISTORY */
+#endif /* HISTORY */
+
+  echo_input_at_read = old_echo_input;
+  expand_aliases = old_expand_aliases;
+
+  current_command_line_count = orig_line_count;
+  shell_input_line_terminator = orig_input_terminator;
+
+  if (flags & 1)
+    parser_state &= ~PST_COMPASSIGN;
+
+  if (wl == &parse_string_error)
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      if (interactive_shell == 0 && posixly_correct)
+       jump_to_top_level (FORCE_EOF);
+      else
+       jump_to_top_level (DISCARD);
+    }
+
+  return (REVERSE_LIST (wl, WORD_LIST *));
+}
+
+static char *
+parse_compound_assignment (retlenp)
+     int *retlenp;
+{
+  WORD_LIST *wl, *rl;
+  int tok, orig_line_number, orig_token_size;
+  char *saved_token, *ret;
+
+  saved_token = token;
+  orig_token_size = token_buffer_size;
+  orig_line_number = line_number;
+
+  last_read_token = WORD;      /* WORD to allow reserved words here */
+
+  token = (char *)NULL;
+  token_buffer_size = 0;
+
+  wl = (WORD_LIST *)NULL;      /* ( */
+  parser_state |= PST_COMPASSIGN;
+
+  while ((tok = read_token (READ)) != ')')
+    {
+      if (tok == '\n')                 /* Allow newlines in compound assignments */
+       {
+         if (SHOULD_PROMPT ())
+           prompt_again ();
+         continue;
+       }
+      if (tok != WORD && tok != ASSIGNMENT_WORD)
+       {
+         current_token = tok;  /* for error reporting */
+         if (tok == yacc_EOF)  /* ( */
+           parser_error (orig_line_number, _("unexpected EOF while looking for matching `)'"));
+         else
+           yyerror(NULL);      /* does the right thing */
+         if (wl)
+           dispose_words (wl);
+         wl = &parse_string_error;
+         break;
+       }
+      wl = make_word_list (yylval.word, wl);
+    }
+
+  FREE (token);
+  token = saved_token;
+  token_buffer_size = orig_token_size;
+
+  parser_state &= ~PST_COMPASSIGN;
+
+  if (wl == &parse_string_error)
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      last_read_token = '\n';  /* XXX */
+      if (interactive_shell == 0 && posixly_correct)
+       jump_to_top_level (FORCE_EOF);
+      else
+       jump_to_top_level (DISCARD);
+    }
+
+  last_read_token = WORD;
+  if (wl)
+    {
+      rl = REVERSE_LIST (wl, WORD_LIST *);
+      ret = string_list (rl);
+      dispose_words (rl);
+    }
+  else
+    ret = (char *)NULL;
+
+  if (retlenp)
+    *retlenp = (ret && *ret) ? strlen (ret) : 0;
+  return ret;
+}
+
+/************************************************
+ *                                             *
+ *   SAVING AND RESTORING PARTIAL PARSE STATE   *
+ *                                             *
+ ************************************************/
+
+sh_parser_state_t *
+save_parser_state (ps)
+     sh_parser_state_t *ps;
+{
+#if defined (ARRAY_VARS)
+  SHELL_VAR *v;
+#endif
+
+  if (ps == 0)
+    ps = (sh_parser_state_t *)xmalloc (sizeof (sh_parser_state_t));
+  if (ps == 0)
+    return ((sh_parser_state_t *)NULL);
+
+  ps->parser_state = parser_state;
+  ps->token_state = save_token_state ();
+
+  ps->input_line_terminator = shell_input_line_terminator;
+  ps->eof_encountered = eof_encountered;
+
+  ps->current_command_line_count = current_command_line_count;
+
+#if defined (HISTORY)
+  ps->remember_on_history = remember_on_history;
+#  if defined (BANG_HISTORY)
+  ps->history_expansion_inhibited = history_expansion_inhibited;
+#  endif
+#endif
+
+  ps->last_command_exit_value = last_command_exit_value;
+#if defined (ARRAY_VARS)
+  v = find_variable ("PIPESTATUS");
+  if (v && array_p (v) && array_cell (v))
+    ps->pipestatus = array_copy (array_cell (v));
+  else
+    ps->pipestatus = (ARRAY *)NULL;
+#endif
+    
+  ps->last_shell_builtin = last_shell_builtin;
+  ps->this_shell_builtin = this_shell_builtin;
+
+  ps->expand_aliases = expand_aliases;
+  ps->echo_input_at_read = echo_input_at_read;
+
+  return (ps);
+}
+
+void
+restore_parser_state (ps)
+     sh_parser_state_t *ps;
+{
+#if defined (ARRAY_VARS)
+  SHELL_VAR *v;
+#endif
+
+  if (ps == 0)
+    return;
+
+  parser_state = ps->parser_state;
+  if (ps->token_state)
+    {
+      restore_token_state (ps->token_state);
+      free (ps->token_state);
+    }
+
+  shell_input_line_terminator = ps->input_line_terminator;
+  eof_encountered = ps->eof_encountered;
+
+  current_command_line_count = ps->current_command_line_count;
+
+#if defined (HISTORY)
+  remember_on_history = ps->remember_on_history;
+#  if defined (BANG_HISTORY)
+  history_expansion_inhibited = ps->history_expansion_inhibited;
+#  endif
+#endif
+
+  last_command_exit_value = ps->last_command_exit_value;
+#if defined (ARRAY_VARS)
+  v = find_variable ("PIPESTATUS");
+  if (v && array_p (v) && array_cell (v))
+    {
+      array_dispose (array_cell (v));
+      var_setarray (v, ps->pipestatus);
+    }
+#endif
+
+  last_shell_builtin = ps->last_shell_builtin;
+  this_shell_builtin = ps->this_shell_builtin;
+
+  expand_aliases = ps->expand_aliases;
+  echo_input_at_read = ps->echo_input_at_read;
+}
+
+/************************************************
+ *                                             *
+ *     MULTIBYTE CHARACTER HANDLING            *
+ *                                             *
+ ************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+static void
+set_line_mbstate ()
+{
+  int i, previ, len, c;
+  mbstate_t mbs, prevs;
+  size_t mbclen;
+
+  if (shell_input_line == NULL)
+    return;
+  len = strlen (shell_input_line);     /* XXX - shell_input_line_len ? */
+  FREE (shell_input_line_property);
+  shell_input_line_property = (char *)xmalloc (len + 1);
+
+  memset (&prevs, '\0', sizeof (mbstate_t));
+  for (i = previ = 0; i < len; i++)
+    {
+      mbs = prevs;
+
+      c = shell_input_line[i];
+      if (c == EOF)
+       {
+         int j;
+         for (j = i; j < len; j++)
+           shell_input_line_property[j] = 1;
+         break;
+       }
+
+      mbclen = mbrlen (shell_input_line + previ, i - previ + 1, &mbs);
+      if (mbclen == 1 || mbclen == (size_t)-1)
+       {
+         mbclen = 1;
+         previ = i + 1;
+       }
+      else if (mbclen == (size_t)-2)
+        mbclen = 0;
+      else if (mbclen > 1)
+       {
+         mbclen = 0;
+         previ = i + 1;
+         prevs = mbs;
+       }
+      else
+       {
+         /* XXX - what to do if mbrlen returns 0? (null wide character) */
+         int j;
+         for (j = i; j < len; j++)
+           shell_input_line_property[j] = 1;
+         break;
+       }
+
+      shell_input_line_property[i] = mbclen;
+    }
+}
+#endif /* HANDLE_MULTIBYTE */
diff --git a/subst.c b/subst.c
index 02c7a8f693b37bb2d6e9517c2d44f02331ea225e..fabaf155a37ed01983aab02f461030fb05c026b0 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -1000,9 +1000,6 @@ extract_array_assignment_list (string, sindex)
      char *string;
      int *sindex;
 {
-#if 0
-  return (extract_delimited_string (string, sindex, "(", (char *)NULL, ")", 0));
-#else
   int slen;
   char *ret;
 
@@ -1013,7 +1010,6 @@ extract_array_assignment_list (string, sindex)
       *sindex = slen - 1;
       return ret;
     }
-#endif
 }
 #endif