]> git.ipfire.org Git - thirdparty/bash.git/commitdiff
commit bash-20101124 snapshot
authorChet Ramey <chet.ramey@case.edu>
Tue, 13 Dec 2011 03:10:33 +0000 (22:10 -0500)
committerChet Ramey <chet.ramey@case.edu>
Tue, 13 Dec 2011 03:10:33 +0000 (22:10 -0500)
69 files changed:
CHANGES
CHANGES-4.2
CWRU/CWRU.chlog
CWRU/CWRU.chlog~
MANIFEST
MANIFEST~
Makefile.in
Makefile.in~
NEWS
NEWS-4.2
alias.c
alias.c~ [new file with mode: 0644]
builtins/evalstring.c
builtins/printf.def
builtins/test.def
builtins/test.def~
doc/bash.1
doc/bash.1~
doc/bashref.texi
doc/bashref.texi~
externs.h
externs.h~
general.h
general.h~ [new file with mode: 0644]
include/shmbchar.h [new file with mode: 0644]
include/shmbchar.h~ [new file with mode: 0644]
include/shmbutil.h
include/shmbutil.h~ [new file with mode: 0644]
lib/glob/smatch.c
lib/glob/smatch.c~ [new file with mode: 0644]
lib/readline/doc/readline.3
lib/readline/doc/readline.3~
lib/readline/doc/rluser.texi
lib/readline/doc/rluser.texi~
lib/readline/kill.c
lib/readline/kill.c~
lib/sh/Makefile.in
lib/sh/Makefile.in~
lib/sh/fnxform.c
lib/sh/shmbchar.c [new file with mode: 0644]
lib/sh/shmbchar.c.save1 [new file with mode: 0644]
lib/sh/shmbchar.c~ [new file with mode: 0644]
lib/sh/unicode.c
lib/sh/unicode.c~
locale.c
parse.y
parse.y~
sig.c
subst.c
subst.c.orig [new file with mode: 0644]
subst.c~
subst.h
subst.h~
tests/RUN-ONE-TEST
tests/exp.right
tests/exp.tests
tests/exp.tests~ [new file with mode: 0644]
tests/exp4.sub [new file with mode: 0644]
tests/intl.right
tests/intl.tests
tests/intl.tests~ [new file with mode: 0644]
tests/intl2.sub [new file with mode: 0644]
unwind_prot.c
variables.c
variables.c~
variables.h
variables.h~
y.tab.c
y.tab.h

diff --git a/CHANGES b/CHANGES
index 2c64072b56a20310ccd25080bfaf905945e7cc17..5c2de170bb45b2e641c773e2f3d824cea6d0ae23 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -260,7 +260,7 @@ u.  History expansion no longer expands the `$!' variable expansion.
 v.  Posix mode shells no longer exit if a variable assignment error occurs
     with an assignment preceding a command that is not a special builtin.
 
-w.  Non-interactive mode shells exit if -u is enabled an an attempt is made
+w.  Non-interactive mode shells exit if -u is enabled and an attempt is made
     to use an unset variable with the % or # expansions, the `//', `^', or
     `,' expansions, or the parameter length expansion.
 
index dc8fe45dd48da6d0d5ec5708f342cad1cb6f79a0..203612514927834934651deeeb919532d47d0af0 100644 (file)
@@ -260,7 +260,7 @@ u.  History expansion no longer expands the `$!' variable expansion.
 v.  Posix mode shells no longer exit if a variable assignment error occurs
     with an assignment preceding a command that is not a special builtin.
 
-w.  Non-interactive mode shells exit if -u is enabled an an attempt is made
+w.  Non-interactive mode shells exit if -u is enabled and an attempt is made
     to use an unset variable with the % or # expansions, the `//', `^', or
     `,' expansions, or the parameter length expansion.
 
index ab580eea7faaff9174d69fb218d4d0d4b3ff1f98..c308da35d460d586ce64e2feb16eec70eb67e260 100644 (file)
@@ -10640,3 +10640,141 @@ lib/readline/complete.c
          users_dirname gets tacked back onto the beginning of the possible
          completions and then requoted.  Bug reported by Andreas Schwab
          <schwab@linux-m68k.org>
+
+                                  11/22
+                                  -----
+lib/readline/parens.c
+       - the `blink-matching-paren' variable should default to off
+
+                                  11/23
+                                  -----
+subst.h
+       - add extern declaration for close_new_fifos()
+
+lib/sh/fnxform.c
+       - fix curencoding to return the character past the `.', not a string
+         beginning with `.'
+
+lib/sh/unicode.c
+       - fix stub_charset to do the same cut-off at `@' as curencoding().
+         These two functions should be combined
+
+builtins/printf.def
+       - document new %(datefmt)T modifier in help text
+
+                                  11/24
+                                  -----
+parse.y
+       - fix `W' case in decode_prompt_string: memmove was copying one too
+         few bytes and missed the closing NUL.  Bug report from Tim Mooney
+         <Tim.Mooney@ndsu.edu>
+
+                                  11/26
+                                  -----
+subst.c
+       - in expand_word_internal, don't add quoted nulls to partially-
+         quoted strings if the word will not be subjected to word splitting
+         later (which will remove the quoted null).  Fixes bug reported by
+         Rocky Bernstein <rocky.bernstein@gmail.com>
+
+                                  11/28
+                                  -----
+subst.c
+       - change multibyte case of match_pattern to revert to match_upattern
+         if neither the pattern nor the string has any multibyte characters
+
+alias.c
+       - fix tests of backslash-escaped characters in skipquotes, skipws,
+         rd_token to check for backslash at EOS and not go past the end.
+         Fixes debian bug 603696 reported by Tim Small <tim@buttersideup.com>
+
+include/shmbchar.h
+       - new file, mbchar.h from gnulib minus the <stdbool.h> include
+
+lib/sh/shmbchar.c
+       - new file, mbchar.c from gnulib with additions
+       - moved mbstrlen from subst.c to here, changed initialization of mbs
+       - change mbstrlen to use is_basic to avoid calls to mbrlen for ASCII
+         chars; code hints from gnulib
+       - don't copy mbs and mbsbak if we're not calling mbrlen
+
+                                  11/29
+                                  -----
+lib/glob/smatch.c
+       - change xstrmatch to use internal_strmatch() if the pattern and
+         string don't have any multibyte characters
+
+                                  11/30
+                                  -----
+include/shmbutil.h
+       - change ADVANCE_CHAR and ADVANCE_CHAR_P macros to use is_basic and
+         only call mbrlen and copy state and state_bak if is_basic returns
+         false (non-ASCII).  Called all over the place.
+       - change rest of macros except BACKUP_CHAR and BACKUP_CHAR_P in the
+         same way
+
+                                  12/2
+                                  ----
+subst.c
+       - audit all calls to string_list and make sure caller can handle a
+         NULL return value.  Fixes bug reported by David Rochberg
+         <rochberg@google.com>
+
+general.h
+       - change sh_wassign_func_t to take an additional argument: an int
+         flags word
+
+subst.c
+       - change do_word_assignment to take an additional argument to match
+         wassign_func_t; change callers
+       - change call to (*assign_func) in expand_word_list_internal to match
+         new wassign_func_t prototype
+       - (*assign_func) passes 1 as additional arg if the simple command is
+         a builtin or function, in which case the assignment to the
+         temporary env should take effect
+
+variables.c
+       - change assign_in_env to take an additional argument to match
+         wassign_func_t; change callers
+       - move call to sv_ifs from dispose_temporary_env to
+         dispose_used_env_vars; we don't need to do it if called from
+         merge_temporary_env
+
+                                  12/3
+                                  ----
+variables.c
+       - change dispose_temporary_env to maintain a list (tempvar_list) of
+         variables that need to be handled specially.  If a variable that
+         gets freed by push_temp_var or propagate_temp_var is one of the
+         variables that the shell handles specially (IFS, LANG, etc.), it's
+         stored on the list.  For each variable in this list,
+         dispose_temp_var calls stupidly_hack_special_variables.
+       - assign_in_env calls stupidly_hack_special_variables if flags arg
+         is non-zero, so variable assignments affect current shell
+         execution environment if a builtin or function is being executed.
+         Fixes bug reported by Bruno Haible <bruno@clisp.org>
+
+                                  12/5
+                                  ----
+subst.c
+       - use mbsmbchar on both string and pattern in match_pattern instead
+         of strlen and mbstrlen; only go through the strings once
+
+                                  12/6
+                                  ----
+lib/readline/kill.c
+       - in rl_yank_last_arg, only switch directions if the `count'
+         argument is < 0, not < 1.  This makes explicit count arguments of
+         0 work as expected.  Fixes bug reported by Dennis Williamson
+         <dennistwilliamson@gmail.com>
+
+doc/bash.1,lib/readline/doc/{readline.3,rluser.texi}
+       - fix documentation for yank-last-arg to make it clear how the count
+         argument is set and how second and subsequent calls treat any
+         numeric argument
+
+doc/{bash.1,bashref.texi}
+       - slight changes to the description of test
+       - change \(bv to `|'; it seems that many `internationalized' versions
+         of groff don't render that as a vertical bar.  Fixes Debian bug
+         603805
index 4a12b21bad85f0a3cba19e799dce6883accef559..1f8f8c674381bf9fe1b24df906ff653fadea682d 100644 (file)
@@ -10633,3 +10633,142 @@ lib/glob/gmisc.c
          fix from Andreas Schwab <schwab@linux-m68k.org>
        - use WLPAREN and WRPAREN in multibyte character environments
        - fixed typos using L'cc' in a non-wide-char environment
+
+lib/readline/complete.c
+       - fix rl_filename_completion_function to dequote users_dirname if
+         there is a filename dequoting function (as well as dirname), since
+         users_dirname gets tacked back onto the beginning of the possible
+         completions and then requoted.  Bug reported by Andreas Schwab
+         <schwab@linux-m68k.org>
+
+                                  11/22
+                                  -----
+lib/readline/parens.c
+       - the `blink-matching-paren' variable should default to off
+
+                                  11/23
+                                  -----
+subst.h
+       - add extern declaration for close_new_fifos()
+
+lib/sh/fnxform.c
+       - fix curencoding to return the character past the `.', not a string
+         beginning with `.'
+
+lib/sh/unicode.c
+       - fix stub_charset to do the same cut-off at `@' as curencoding().
+         These two functions should be combined
+
+builtins/printf.def
+       - document new %(datefmt)T modifier in help text
+
+                                  11/24
+                                  -----
+parse.y
+       - fix `W' case in decode_prompt_string: memmove was copying one too
+         few bytes and missed the closing NUL.  Bug report from Tim Mooney
+         <Tim.Mooney@ndsu.edu>
+
+                                  11/26
+                                  -----
+subst.c
+       - in expand_word_internal, don't add quoted nulls to partially-
+         quoted strings if the word will not be subjected to word splitting
+         later (which will remove the quoted null).  Fixes bug reported by
+         Rocky Bernstein <rocky.bernstein@gmail.com>
+
+                                  11/28
+                                  -----
+subst.c
+       - change multibyte case of match_pattern to revert to match_upattern
+         if neither the pattern nor the string has any multibyte characters
+
+alias.c
+       - fix tests of backslash-escaped characters in skipquotes, skipws,
+         rd_token to check for backslash at EOS and not go past the end.
+         Fixes debian bug 603696 reported by Tim Small <tim@buttersideup.com>
+
+include/shmbchar.h
+       - new file, mbchar.h from gnulib minus the <stdbool.h> include
+
+lib/sh/shmbchar.c
+       - new file, mbchar.c from gnulib with additions
+       - moved mbstrlen from subst.c to here, changed initialization of mbs
+       - change mbstrlen to use is_basic to avoid calls to mbrlen for ASCII
+         chars; code hints from gnulib
+       - don't copy mbs and mbsbak if we're not calling mbrlen
+
+                                  11/29
+                                  -----
+lib/glob/smatch.c
+       - change xstrmatch to use internal_strmatch() if the pattern and
+         string don't have any multibyte characters
+
+                                  11/30
+                                  -----
+include/shmbutil.h
+       - change ADVANCE_CHAR and ADVANCE_CHAR_P macros to use is_basic and
+         only call mbrlen and copy state and state_bak if is_basic returns
+         false (non-ASCII).  Called all over the place.
+       - change rest of macros except BACKUP_CHAR and BACKUP_CHAR_P in the
+         same way
+
+                                  12/2
+                                  ----
+subst.c
+       - audit all calls to string_list and make sure caller can handle a
+         NULL return value.  Fixes bug reported by David Rochberg
+         <rochberg@google.com>
+
+general.h
+       - change sh_wassign_func_t to take an additional argument: an int
+         flags word
+
+subst.c
+       - change do_word_assignment to take an additional argument to match
+         wassign_func_t; change callers
+       - change call to (*assign_func) in expand_word_list_internal to match
+         new wassign_func_t prototype
+       - (*assign_func) passes 1 as additional arg if the simple command is
+         a builtin or function, in which case the assignment to the
+         temporary env should take effect
+
+variables.c
+       - change assign_in_env to take an additional argument to match
+         wassign_func_t; change callers
+       - move call to sv_ifs from dispose_temporary_env to
+         dispose_used_env_vars; we don't need to do it if called from
+         merge_temporary_env
+
+                                  12/3
+                                  ----
+variables.c
+       - change dispose_temporary_env to maintain a list (tempvar_list) of
+         variables that need to be handled specially.  If a variable that
+         gets freed by push_temp_var or propagate_temp_var is one of the
+         variables that the shell handles specially (IFS, LANG, etc.), it's
+         stored on the list.  For each variable in this list,
+         dispose_temp_var calls stupidly_hack_special_variables.
+       - assign_in_env calls stupidly_hack_special_variables if flags arg
+         is non-zero, so variable assignments affect current shell
+         execution environment if a builtin or function is being executed.
+         Fixes bug reported by Bruno Haible <bruno@clisp.org>
+
+                                  12/5
+                                  ----
+subst.c
+       - use mbsmbchar on both string and pattern in match_pattern instead
+         of strlen and mbstrlen; only go through the strings once
+
+                                  12/6
+                                  ----
+lib/readline/kill.c
+       - in rl_yank_last_arg, only switch directions if the `count'
+         argument is < 0, not < 1.  This makes explicit count arguments of
+         0 work as expected.  Fixes bug reported by Dennis Williamson
+         <dennistwilliamson@gmail.com>
+
+doc/bash.1,lib/readline/doc/{readline.3,rluser.texi}
+       - fix documentation for yank-last-arg to make it clear how the count
+         argument is set and how second and subsequent calls treat any
+         numeric argument
index 9613250efa8d406d0cfcc683f05806ce64bcce29..a63b514087c6b932b50f81ade2980d1263ea3689 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -220,6 +220,7 @@ include/posixselect.h       f
 include/posixstat.h    f
 include/posixtime.h    f
 include/posixwait.h    f
+include/shmbchar.h     f
 include/shmbutil.h     f
 include/shtty.h                f
 include/stdc.h         f
@@ -411,6 +412,7 @@ lib/sh/pathphys.c   f
 lib/sh/rename.c                f
 lib/sh/setlinebuf.c    f
 lib/sh/shmatch.c       f
+lib/sh/shmbchar.c      f
 lib/sh/shquote.c       f
 lib/sh/shtty.c         f
 lib/sh/snprintf.c      f
@@ -852,6 +854,7 @@ tests/exp.right             f
 tests/exp1.sub         f
 tests/exp2.sub         f
 tests/exp3.sub         f
+tests/exp4.sub         f
 tests/extglob.tests    f
 tests/extglob.right    f
 tests/extglob1.sub     f
@@ -901,6 +904,7 @@ tests/input-line.sub        f
 tests/input.right      f
 tests/intl.tests       f
 tests/intl1.sub                f
+tests/intl2.sub                f
 tests/intl.right       f
 tests/iquote.tests     f
 tests/iquote.right     f
index cb81bb045083b2bbc0861e9118064dfdebb05996..864e719ddd68d108fb14a08caceeb88b8e74a229 100644 (file)
--- a/MANIFEST~
+++ b/MANIFEST~
@@ -220,6 +220,7 @@ include/posixselect.h       f
 include/posixstat.h    f
 include/posixtime.h    f
 include/posixwait.h    f
+include/shmbchar.h     f
 include/shmbutil.h     f
 include/shtty.h                f
 include/stdc.h         f
@@ -411,6 +412,7 @@ lib/sh/pathphys.c   f
 lib/sh/rename.c                f
 lib/sh/setlinebuf.c    f
 lib/sh/shmatch.c       f
+lib/sh/shmbchar.c      f
 lib/sh/shquote.c       f
 lib/sh/shtty.c         f
 lib/sh/snprintf.c      f
@@ -852,6 +854,7 @@ tests/exp.right             f
 tests/exp1.sub         f
 tests/exp2.sub         f
 tests/exp3.sub         f
+tests/exp4.sub         f
 tests/extglob.tests    f
 tests/extglob.right    f
 tests/extglob1.sub     f
@@ -950,6 +953,8 @@ tests/posixexp.tests        f
 tests/posixexp.right   f
 tests/posixexp1.sub    f
 tests/posixexp2.sub    f
+tests/posixexp2.tests  f
+tests/posixexp2.right  f
 tests/posixpat.tests   f
 tests/posixpat.right   f
 tests/posixpipe.tests  f
@@ -1045,6 +1050,7 @@ tests/run-nquote4 f
 tests/run-nquote5      f
 tests/run-posix2       f
 tests/run-posixexp     f
+tests/run-posixexp2    f
 tests/run-posixpat     f
 tests/run-posixpipe    f
 tests/run-precedence   f
index 0319d7cf783d81140e7b5976278d359125e3c14e..9f9276d487a5683e85cc34ea732dd9f03394699e 100644 (file)
@@ -215,7 +215,7 @@ SHLIB_SOURCE =      ${SH_LIBSRC}/clktck.c ${SH_LIBSRC}/getcwd.c \
                ${SH_LIBSRC}/ufuncs.c ${SH_LIBSRC}/dprintf.c \
                ${SH_LIBSRC}/input_avail.c ${SH_LIBSRC}/mbscasecmp.c \
                ${SH_LIBSRC}/fnxform.c ${SH_LIBSRC}/unicode.c \
-               ${SH_LIBSRC}/wcswidth.c
+               ${SH_LIBSRC}/wcswidth.c ${SH_LIBSRC}/shmbchar.c
 
 SHLIB_LIB = -lsh
 SHLIB_LIBNAME = libsh.a
@@ -973,7 +973,7 @@ make_cmd.o: config.h bashtypes.h ${BASHINCDIR}/filecntl.h bashansi.h
 make_cmd.o: command.h ${BASHINCDIR}/stdc.h general.h xmalloc.h error.h flags.h make_cmd.h
 make_cmd.o: variables.h arrayfunc.h conftypes.h array.h hashlib.h subst.h input.h externs.h
 make_cmd.o: jobs.h quit.h siglist.h syntax.h dispose_cmd.h parser.h
-make_cmd.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/ocache.h
+make_cmd.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h ${BASHINCDIR}/ocache.h
 y.tab.o: config.h bashtypes.h bashansi.h ${BASHINCDIR}/ansi_stdlib.h ${BASHINCDIR}/memalloc.h
 y.tab.o: shell.h syntax.h config.h bashjmp.h ${BASHINCDIR}/posixjmp.h command.h ${BASHINCDIR}/stdc.h error.h
 y.tab.o: general.h xmalloc.h bashtypes.h variables.h arrayfunc.h conftypes.h array.h hashlib.h
@@ -988,7 +988,7 @@ pathexp.o: quit.h ${BASHINCDIR}/maxpath.h unwind_prot.h dispose_cmd.h
 pathexp.o: make_cmd.h subst.h sig.h pathnames.h externs.h
 pathexp.o: pathexp.h flags.h 
 pathexp.o: $(GLOB_LIBSRC)/glob.h $(GLOB_LIBSRC)/strmatch.h
-pathexp.o: ${BASHINCDIR}/shmbutil.h
+pathexp.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
 print_cmd.o: config.h bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 print_cmd.o: shell.h syntax.h config.h bashjmp.h ${BASHINCDIR}/posixjmp.h command.h ${BASHINCDIR}/stdc.h error.h
 print_cmd.o: general.h xmalloc.h bashtypes.h variables.h arrayfunc.h conftypes.h array.h hashlib.h
@@ -1030,7 +1030,7 @@ subst.o: flags.h jobs.h siglist.h execute_cmd.h ${BASHINCDIR}/filecntl.h trap.h
 subst.o: mailcheck.h input.h $(DEFSRC)/getopt.h $(DEFSRC)/common.h
 subst.o: bashline.h bashhist.h ${GLOB_LIBSRC}/strmatch.h
 subst.o: ${BASHINCDIR}/chartypes.h
-subst.o: ${BASHINCDIR}/shmbutil.h
+subst.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
 subst.o: ${DEFDIR}/builtext.h
 test.o: bashtypes.h ${BASHINCDIR}/posixstat.h ${BASHINCDIR}/filecntl.h
 test.o: shell.h syntax.h config.h bashjmp.h ${BASHINCDIR}/posixjmp.h command.h ${BASHINCDIR}/stdc.h error.h
@@ -1089,7 +1089,7 @@ arrayfunc.o: general.h xmalloc.h bashtypes.h variables.h arrayfunc.h conftypes.h
 arrayfunc.o: quit.h ${BASHINCDIR}/maxpath.h unwind_prot.h dispose_cmd.h
 arrayfunc.o: make_cmd.h subst.h sig.h pathnames.h externs.h pathexp.h
 arrayfunc.o: $(DEFSRC)/common.h
-arrayfunc.o: ${BASHINCDIR}/shmbutil.h
+arrayfunc.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
 assoc.o: config.h bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 assoc.o: shell.h syntax.h config.h bashjmp.h ${BASHINCDIR}/posixjmp.h
 assoc.o: command.h ${BASHINCDIR}/stdc.h error.h
@@ -1103,7 +1103,7 @@ braces.o: shell.h syntax.h config.h bashjmp.h ${BASHINCDIR}/posixjmp.h command.h
 braces.o: general.h xmalloc.h bashtypes.h variables.h arrayfunc.h conftypes.h array.h hashlib.h
 braces.o: quit.h ${BASHINCDIR}/maxpath.h unwind_prot.h dispose_cmd.h
 braces.o: make_cmd.h subst.h sig.h pathnames.h externs.h 
-braces.o: ${BASHINCDIR}/shmbutil.h
+braces.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
 alias.o: config.h bashansi.h ${BASHINCDIR}/ansi_stdlib.h command.h ${BASHINCDIR}/stdc.h
 alias.o: general.h xmalloc.h bashtypes.h externs.h alias.h
 alias.o: pcomplete.h 
index 04cec51e0f2e93ec44ab14f7247341d203f35d04..fefec1ca040a54b33ce0edd706f2135ccea4578b 100644 (file)
@@ -215,7 +215,7 @@ SHLIB_SOURCE =      ${SH_LIBSRC}/clktck.c ${SH_LIBSRC}/getcwd.c \
                ${SH_LIBSRC}/ufuncs.c ${SH_LIBSRC}/dprintf.c \
                ${SH_LIBSRC}/input_avail.c ${SH_LIBSRC}/mbscasecmp.c \
                ${SH_LIBSRC}/fnxform.c ${SH_LIBSRC}/unicode.c \
-               ${SH_LIBSRC}/wcswidth.c
+               ${SH_LIBSRC}/wcswidth.c ${SH_LIBSRC}/shmbchar.c
 
 SHLIB_LIB = -lsh
 SHLIB_LIBNAME = libsh.a
@@ -522,7 +522,7 @@ CREATED_SUPPORT = signames.h recho$(EXEEXT) zecho$(EXEEXT) printenv$(EXEEXT) \
                  mksyntax${EXEEXT} syntax.c $(VERSPROG) $(VERSOBJ) \
                  buildversion.o mksignames.o signames.o buildsignames.o
 CREATED_CONFIGURE = config.h config.cache config.status config.log \
-                   stamp-h po/POTFILES
+                   stamp-h po/POTFILES config.status.lineno
 CREATED_MAKEFILES = Makefile builtins/Makefile doc/Makefile \
                    lib/readline/Makefile lib/glob/Makefile \
                    lib/sh/Makefile lib/tilde/Makefile lib/malloc/Makefile \
diff --git a/NEWS b/NEWS
index 89036abb8b4fffab5af1a5e9a07310abe73446d9..028ba42a70de70ae0d3c747cc55004230637c492 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -67,7 +67,7 @@ u.  History expansion no longer expands the `$!' variable expansion.
 v.  Posix mode shells no longer exit if a variable assignment error occurs
     with an assignment preceding a command that is not a special builtin.
 
-w.  Non-interactive mode shells exit if -u is enabled an an attempt is made
+w.  Non-interactive mode shells exit if -u is enabled and an attempt is made
     to use an unset variable with the % or # expansions, the `//', `^', or
     `,' expansions, or the parameter length expansion.
 
index 4cb58b2cc69e04abecffe54d78eb64d91fdf4f93..e704a658a86e9c925caa4e205e65e37c0a55f75f 100644 (file)
--- a/NEWS-4.2
+++ b/NEWS-4.2
@@ -64,7 +64,7 @@ u.  History expansion no longer expands the `$!' variable expansion.
 v.  Posix mode shells no longer exit if a variable assignment error occurs
     with an assignment preceding a command that is not a special builtin.
 
-w.  Non-interactive mode shells exit if -u is enabled an an attempt is made
+w.  Non-interactive mode shells exit if -u is enabled and an attempt is made
     to use an unset variable with the % or # expansions, the `//', `^', or
     `,' expansions, or the parameter length expansion.
 
diff --git a/alias.c b/alias.c
index 558665e5040a11010f53e5fcaccd221977b89791..6c953760f1036e2fb4820b4bcfb53bce37407a34 100644 (file)
--- a/alias.c
+++ b/alias.c
@@ -319,6 +319,8 @@ skipquotes (string, start)
       if (string[i] == '\\')
        {
          i++;          /* skip backslash-quoted quote characters, too */
+         if (string[i] == 0)
+           break;
          continue;
        }
 
@@ -364,6 +366,8 @@ skipws (string, start)
       if (string[i] == '\\')
        {
          peekc = string[i+1];
+         if (peekc == 0)
+           break;
          if (ISLETTER (peekc))
            backslash_quoted_word++;    /* this is a backslash-quoted word */
          else
@@ -429,6 +433,8 @@ rd_token (string, start)
       if (string[i] == '\\')
        {
          i++;  /* skip backslash-escaped character */
+         if (string[i] == 0)
+           break;
          continue;
        }
 
diff --git a/alias.c~ b/alias.c~
new file mode 100644 (file)
index 0000000..89e533f
--- /dev/null
+++ b/alias.c~
@@ -0,0 +1,576 @@
+/* alias.c -- Not a full alias, but just the kind that we use in the
+   shell.  Csh style alias is somewhere else (`over there, in a box'). */
+
+/* Copyright (C) 1987-2009 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#if defined (ALIAS)
+
+#if defined (HAVE_UNISTD_H)
+#  ifdef _MINIX
+#    include <sys/types.h>
+#  endif
+#  include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include "chartypes.h"
+#include "bashansi.h"
+#include "command.h"
+#include "general.h"
+#include "externs.h"
+#include "alias.h"
+
+#if defined (PROGRAMMABLE_COMPLETION)
+#  include "pcomplete.h"
+#endif
+
+#define ALIAS_HASH_BUCKETS     16      /* must be power of two */
+
+typedef int sh_alias_map_func_t __P((alias_t *));
+
+static void free_alias_data __P((PTR_T));
+static alias_t **map_over_aliases __P((sh_alias_map_func_t *));
+static void sort_aliases __P((alias_t **));
+static int qsort_alias_compare __P((alias_t **, alias_t **));
+
+#if defined (READLINE)
+static int skipquotes __P((char *, int));
+static int skipws __P((char *, int));
+static int rd_token __P((char *, int));
+#endif
+
+/* Non-zero means expand all words on the line.  Otherwise, expand
+   after first expansion if the expansion ends in a space. */
+int alias_expand_all = 0;
+
+/* The list of aliases that we have. */
+HASH_TABLE *aliases = (HASH_TABLE *)NULL;
+
+void
+initialize_aliases ()
+{
+  if (aliases == 0)
+    aliases = hash_create (ALIAS_HASH_BUCKETS);
+}
+
+/* Scan the list of aliases looking for one with NAME.  Return NULL
+   if the alias doesn't exist, else a pointer to the alias_t. */
+alias_t *
+find_alias (name)
+     char *name;
+{
+  BUCKET_CONTENTS *al;
+
+  if (aliases == 0)
+    return ((alias_t *)NULL);
+
+  al = hash_search (name, aliases, 0);
+  return (al ? (alias_t *)al->data : (alias_t *)NULL);
+}
+
+/* Return the value of the alias for NAME, or NULL if there is none. */
+char *
+get_alias_value (name)
+     char *name;
+{
+  alias_t *alias;
+
+  if (aliases == 0)
+    return ((char *)NULL);
+
+  alias = find_alias (name);
+  return (alias ? alias->value : (char *)NULL);
+}
+
+/* Make a new alias from NAME and VALUE.  If NAME can be found,
+   then replace its value. */
+void
+add_alias (name, value)
+     char *name, *value;
+{
+  BUCKET_CONTENTS *elt;
+  alias_t *temp;
+  int n;
+
+  if (!aliases)
+    {
+      initialize_aliases ();
+      temp = (alias_t *)NULL;
+    }
+  else
+    temp = find_alias (name);
+
+  if (temp)
+    {
+      free (temp->value);
+      temp->value = savestring (value);
+      temp->flags &= ~AL_EXPANDNEXT;
+      n = value[strlen (value) - 1];
+      if (n == ' ' || n == '\t')
+       temp->flags |= AL_EXPANDNEXT;
+    }
+  else
+    {
+      temp = (alias_t *)xmalloc (sizeof (alias_t));
+      temp->name = savestring (name);
+      temp->value = savestring (value);
+      temp->flags = 0;
+
+      n = value[strlen (value) - 1];
+      if (n == ' ' || n == '\t')
+       temp->flags |= AL_EXPANDNEXT;
+
+      elt = hash_insert (savestring (name), aliases, HASH_NOSRCH);
+      elt->data = temp;
+#if defined (PROGRAMMABLE_COMPLETION)
+      set_itemlist_dirty (&it_aliases);
+#endif
+    }
+}
+
+/* Delete a single alias structure. */
+static void
+free_alias_data (data)
+     PTR_T data;
+{
+  register alias_t *a;
+
+  a = (alias_t *)data;
+  free (a->value);
+  free (a->name);
+  free (data);
+}
+
+/* Remove the alias with name NAME from the alias table.  Returns
+   the number of aliases left in the table, or -1 if the alias didn't
+   exist. */
+int
+remove_alias (name)
+     char *name;
+{
+  BUCKET_CONTENTS *elt;
+
+  if (aliases == 0)
+    return (-1);
+
+  elt = hash_remove (name, aliases, 0);
+  if (elt)
+    {
+      free_alias_data (elt->data);
+      free (elt->key);         /* alias name */
+      free (elt);              /* XXX */
+#if defined (PROGRAMMABLE_COMPLETION)
+      set_itemlist_dirty (&it_aliases);
+#endif
+      return (aliases->nentries);
+    }
+  return (-1);
+}
+
+/* Delete all aliases. */
+void
+delete_all_aliases ()
+{
+  if (aliases == 0)
+    return;
+
+  hash_flush (aliases, free_alias_data);
+  hash_dispose (aliases);
+  aliases = (HASH_TABLE *)NULL;
+#if defined (PROGRAMMABLE_COMPLETION)
+  set_itemlist_dirty (&it_aliases);
+#endif
+}
+
+/* Return an array of aliases that satisfy the conditions tested by FUNCTION.
+   If FUNCTION is NULL, return all aliases. */
+static alias_t **
+map_over_aliases (function)
+     sh_alias_map_func_t *function;
+{
+  register int i;
+  register BUCKET_CONTENTS *tlist;
+  alias_t *alias, **list;
+  int list_index;
+
+  i = HASH_ENTRIES (aliases);
+  if (i == 0)
+    return ((alias_t **)NULL);
+
+  list = (alias_t **)xmalloc ((i + 1) * sizeof (alias_t *));
+  for (i = list_index = 0; i < aliases->nbuckets; i++)
+    {
+      for (tlist = hash_items (i, aliases); tlist; tlist = tlist->next)
+       {
+         alias = (alias_t *)tlist->data;
+
+         if (!function || (*function) (alias))
+           {
+             list[list_index++] = alias;
+             list[list_index] = (alias_t *)NULL;
+           }
+       }
+    }
+  return (list);
+}
+
+static void
+sort_aliases (array)
+     alias_t **array;
+{
+  qsort (array, strvec_len ((char **)array), sizeof (alias_t *), (QSFUNC *)qsort_alias_compare);
+}
+
+static int
+qsort_alias_compare (as1, as2)
+     alias_t **as1, **as2;
+{
+  int result;
+
+  if ((result = (*as1)->name[0] - (*as2)->name[0]) == 0)
+    result = strcmp ((*as1)->name, (*as2)->name);
+
+  return (result);
+}
+
+/* Return a sorted list of all defined aliases */
+alias_t **
+all_aliases ()
+{
+  alias_t **list;
+
+  if (aliases == 0 || HASH_ENTRIES (aliases) == 0)
+    return ((alias_t **)NULL);
+
+  list = map_over_aliases ((sh_alias_map_func_t *)NULL);
+  if (list)
+    sort_aliases (list);
+  return (list);
+}
+
+char *
+alias_expand_word (s)
+     char *s;
+{
+  alias_t *r;
+
+  r = find_alias (s);
+  return (r ? savestring (r->value) : (char *)NULL);
+}
+
+/* Readline support functions -- expand all aliases in a line. */
+
+#if defined (READLINE)
+
+/* Return non-zero if CHARACTER is a member of the class of characters
+   that are self-delimiting in the shell (this really means that these
+   characters delimit tokens). */
+#define self_delimiting(character) (member ((character), " \t\n\r;|&()"))
+
+/* Return non-zero if CHARACTER is a member of the class of characters
+   that delimit commands in the shell. */
+#define command_separator(character) (member ((character), "\r\n;|&("))
+
+/* If this is 1, we are checking the next token read for alias expansion
+   because it is the first word in a command. */
+static int command_word;
+
+/* This is for skipping quoted strings in alias expansions. */
+#define quote_char(c)  (((c) == '\'') || ((c) == '"'))
+
+/* Consume a quoted string from STRING, starting at string[START] (so
+   string[START] is the opening quote character), and return the index
+   of the closing quote character matching the opening quote character.
+   This handles single matching pairs of unquoted quotes; it could afford
+   to be a little smarter... This skips words between balanced pairs of
+   quotes, words where the first character is quoted with a `\', and other
+   backslash-escaped characters. */
+
+static int
+skipquotes (string, start)
+     char *string;
+     int start;
+{
+  register int i;
+  int delimiter = string[start];
+
+  /* i starts at START + 1 because string[START] is the opening quote
+     character. */
+  for (i = start + 1 ; string[i] ; i++)
+    {
+      if (string[i] == '\\')
+       {
+         i++;          /* skip backslash-quoted quote characters, too */
+         continue;
+       }
+
+      if (string[i] == delimiter)
+       return i;
+    }
+  return (i);
+}
+
+/* Skip the white space and any quoted characters in STRING, starting at
+   START.  Return the new index into STRING, after zero or more characters
+   have been skipped. */
+static int
+skipws (string, start)
+     char *string;
+     int start;
+{
+  register int i;
+  int pass_next, backslash_quoted_word;
+  unsigned char peekc;
+
+  /* skip quoted strings, in ' or ", and words in which a character is quoted
+     with a `\'. */
+  i = backslash_quoted_word = pass_next = 0;
+
+  /* Skip leading whitespace (or separator characters), and quoted words.
+     But save it in the output.  */
+
+  for (i = start; string[i]; i++)
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         continue;
+       }
+
+      if (whitespace (string[i]))
+       {
+         backslash_quoted_word = 0; /* we are no longer in a backslash-quoted word */
+         continue;
+       }
+
+      if (string[i] == '\\')
+       {
+         peekc = string[i+1];
+         if (peekc == 0)
+           break;
+         if (ISLETTER (peekc))
+           backslash_quoted_word++;    /* this is a backslash-quoted word */
+         else
+           pass_next++;
+         continue;
+       }
+
+      /* This only handles single pairs of non-escaped quotes.  This
+        overloads backslash_quoted_word to also mean that a word like
+        ""f is being scanned, so that the quotes will inhibit any expansion
+        of the word. */
+      if (quote_char(string[i]))
+       {
+         i = skipquotes (string, i);
+         /* This could be a line that contains a single quote character,
+            in which case skipquotes () terminates with string[i] == '\0'
+            (the end of the string).  Check for that here. */
+         if (string[i] == '\0')
+           break;
+
+         peekc = string[i + 1];
+         if (ISLETTER (peekc))
+           backslash_quoted_word++;
+         continue;
+       }
+
+      /* If we're in the middle of some kind of quoted word, let it
+        pass through. */
+      if (backslash_quoted_word)
+       continue;
+
+      /* If this character is a shell command separator, then set a hint for
+        alias_expand that the next token is the first word in a command. */
+
+      if (command_separator (string[i]))
+       {
+         command_word++;
+         continue;
+       }
+      break;
+    }
+  return (i);
+}
+
+/* Characters that may appear in a token.  Basically, anything except white
+   space and a token separator. */
+#define token_char(c)  (!((whitespace (string[i]) || self_delimiting (string[i]))))
+
+/* Read from START in STRING until the next separator character, and return
+   the index of that separator.  Skip backslash-quoted characters.  Call
+   skipquotes () for quoted strings in the middle or at the end of tokens,
+   so all characters show up (e.g. foo'' and foo""bar) */
+static int
+rd_token (string, start)
+     char *string;
+     int start;
+{
+  register int i;
+
+  /* From here to next separator character is a token. */
+  for (i = start; string[i] && token_char (string[i]); i++)
+    {
+      if (string[i] == '\\')
+       {
+         i++;  /* skip backslash-escaped character */
+         continue;
+       }
+
+      /* If this character is a quote character, we want to call skipquotes
+        to get the whole quoted portion as part of this word.  That word
+        will not generally match an alias, even if te unquoted word would
+        have.  The presence of the quotes in the token serves then to
+        inhibit expansion. */
+      if (quote_char (string[i]))
+       {
+         i = skipquotes (string, i);
+         /* This could be a line that contains a single quote character,
+            in which case skipquotes () terminates with string[i] == '\0'
+            (the end of the string).  Check for that here. */
+         if (string[i] == '\0')
+           break;
+
+         /* Now string[i] is the matching quote character, and the
+            quoted portion of the token has been scanned. */
+         continue;
+       }
+    }
+  return (i);
+}
+
+/* Return a new line, with any aliases substituted. */
+char *
+alias_expand (string)
+     char *string;
+{
+  register int i, j, start;
+  char *line, *token;
+  int line_len, tl, real_start, expand_next, expand_this_token;
+  alias_t *alias;
+
+  line_len = strlen (string) + 1;
+  line = (char *)xmalloc (line_len);
+  token = (char *)xmalloc (line_len);
+
+  line[0] = i = 0;
+  expand_next = 0;
+  command_word = 1; /* initialized to expand the first word on the line */
+
+  /* Each time through the loop we find the next word in line.  If it
+     has an alias, substitute the alias value.  If the value ends in ` ',
+     then try again with the next word.  Else, if there is no value, or if
+     the value does not end in space, we are done. */
+
+  for (;;)
+    {
+
+      token[0] = 0;
+      start = i;
+
+      /* Skip white space and quoted characters */
+      i = skipws (string, start);
+
+      if (start == i && string[i] == '\0')
+       {
+         free (token);
+         return (line);
+       }
+
+      /* copy the just-skipped characters into the output string,
+        expanding it if there is not enough room. */
+      j = strlen (line);
+      tl = i - start;  /* number of characters just skipped */
+      RESIZE_MALLOCED_BUFFER (line, j, (tl + 1), line_len, (tl + 50));
+      strncpy (line + j, string + start, tl);
+      line[j + tl] = '\0';
+
+      real_start = i;
+
+      command_word = command_word || (command_separator (string[i]));
+      expand_this_token = (command_word || expand_next);
+      expand_next = 0;
+
+      /* Read the next token, and copy it into TOKEN. */
+      start = i;
+      i = rd_token (string, start);
+
+      tl = i - start;  /* token length */
+
+      /* If tl == 0, but we're not at the end of the string, then we have a
+        single-character token, probably a delimiter */
+      if (tl == 0 && string[i] != '\0')
+       {
+         tl = 1;
+         i++;          /* move past it */
+       }
+
+      strncpy (token, string + start, tl);
+      token [tl] = '\0';
+
+      /* If there is a backslash-escaped character quoted in TOKEN,
+        then we don't do alias expansion.  This should check for all
+        other quoting characters, too. */
+      if (mbschr (token, '\\'))
+       expand_this_token = 0;
+
+      /* If we should be expanding here, if we are expanding all words, or if
+        we are in a location in the string where an expansion is supposed to
+        take place, see if this word has a substitution.  If it does, then do
+        the expansion.  Note that we defer the alias value lookup until we
+        are sure we are expanding this token. */
+
+      if ((token[0]) &&
+         (expand_this_token || alias_expand_all) &&
+         (alias = find_alias (token)))
+       {
+         char *v;
+         int vlen, llen;
+
+         v = alias->value;
+         vlen = strlen (v);
+         llen = strlen (line);
+
+         /* +3 because we possibly add one more character below. */
+         RESIZE_MALLOCED_BUFFER (line, llen, (vlen + 3), line_len, (vlen + 50));
+
+         strcpy (line + llen, v);
+
+         if ((expand_this_token && vlen && whitespace (v[vlen - 1])) ||
+             alias_expand_all)
+           expand_next = 1;
+       }
+      else
+       {
+         int llen, tlen;
+
+         llen = strlen (line);
+         tlen = i - real_start; /* tlen == strlen(token) */
+
+         RESIZE_MALLOCED_BUFFER (line, llen, (tlen + 1), line_len, (llen + tlen + 50));
+
+         strncpy (line + llen, string + real_start, tlen);
+         line[llen + tlen] = '\0';
+       }
+      command_word = 0;
+    }
+}
+#endif /* READLINE */
+#endif /* ALIAS */
index 8c9833b7599d1e52758dbdccc7ed7b1111ac3329..333a56ee08bb37b5022462510376fd2754af1ce6 100644 (file)
@@ -1,4 +1,4 @@
-/* evalstring.c - evaluate a string as one or more shell commands. 
+/* evalstring.c - evaluate a string as one or more shell commands. */
 
 /* Copyright (C) 1996-2010 Free Software Foundation, Inc.
 
index 5971d9bb2dc6f25d14e614cfef0567cb6c2661c0..7892cb5a0940acb0ecb11a1348abe3fd8de349df 100644 (file)
@@ -40,6 +40,8 @@ and printf(3), printf interprets:
 
   %b   expand backslash escape sequences in the corresponding argument
   %q   quote the argument in a way that can be reused as shell input
+  %(fmt)T output the date-time string resulting from using FMT as a format
+        string for strftime(3)
 
 Exit Status:
 Returns success unless an invalid option is given or a write or assignment
index 2eea2f505ce22a2e5d13736829da742fde877408..1ebc8187200c8fadf29c14fcf38554d952334970 100644 (file)
@@ -28,7 +28,10 @@ Evaluate conditional expression.
 Exits with a status of 0 (true) or 1 (false) depending on
 the evaluation of EXPR.  Expressions may be unary or binary.  Unary
 expressions are often used to examine the status of a file.  There
-are string operators as well, and numeric comparison operators.
+are string operators and numeric comparison operators as well.
+
+The behavior of test depends on the number of arguments.  Read the
+bash manual page for the complete specification.
 
 File operators:
 
index d355297430b97318bb040b67ff0f4350ad620084..2eea2f505ce22a2e5d13736829da742fde877408 100644 (file)
@@ -1,7 +1,7 @@
 This file is test.def, from which is created test.c.
 It implements the builtin "test" in Bash.
 
-Copyright (C) 1987-2009 Free Software Foundation, Inc.
+Copyright (C) 1987-2010 Free Software Foundation, Inc.
 
 This file is part of GNU Bash, the Bourne Again SHell.
 
index be7abed00c679d01ae354faecdfd948c6d46670e..2b25465dc352615433c11a4830ca39ad3c461e93 100644 (file)
@@ -5,12 +5,12 @@
 .\"    Case Western Reserve University
 .\"    chet@po.cwru.edu
 .\"
-.\"    Last Change: Mon Sep  6 22:07:38 EDT 2010
+.\"    Last Change: Mon Dec  6 22:25:10 EST 2010
 .\"
 .\" bash_builtins, strip all but Built-Ins section
 .if \n(zZ=1 .ig zZ
 .if \n(zY=1 .ig zY
-.TH BASH 1 "2010 September 6" "GNU Bash-4.2"
+.TH BASH 1 "2010 December 6" "GNU Bash-4.2"
 .\"
 .\" There's some problem with having a `@'
 .\" in a tagged paragraph with the BSD man macros.
@@ -476,7 +476,7 @@ A \fItoken\fP that performs a control function.  It is one of the following
 symbols:
 .RS
 .PP
-.if t \fB\(bv\(bv  &  &&  ;  ;;  (  )  |  |&    <newline>\fP
+.if t \fB||  &  &&  ;  ;;  (  )  |  |&    <newline>\fP
 .if n \fB|| & && ; ;; ( ) | |& <newline>\fP
 .RE
 .PD
@@ -586,7 +586,7 @@ of the operators
 .BR & ,
 .BR && ,
 or
-.BR \(bv\(bv ,
+.BR || ,
 and optionally terminated by one of
 .BR ; ,
 .BR & ,
@@ -596,7 +596,7 @@ or
 Of these list operators,
 .B &&
 and
-.B \(bv\(bv
+.B ||
 have equal precedence, followed by
 .B ;
 and
@@ -617,7 +617,7 @@ command to terminate in turn.  The return status is the
 exit status of the last command executed.
 .PP
 AND and OR lists are sequences of one of more pipelines separated by the
-\fB&&\fP and \fB\(bv\(bv\fP control operators, respectively.
+\fB&&\fP and \fB||\fP control operators, respectively.
 AND and OR lists are executed with left associativity.
 An AND list has the form
 .RS
@@ -633,7 +633,7 @@ returns an exit status of zero.
 An OR list has the form
 .RS
 .PP
-\fIcommand1\fP \fB\(bv\(bv\fP \fIcommand2\fP
+\fIcommand1\fP \fB||\fP \fIcommand2\fP
 .PP
 .RE
 .PP
@@ -763,8 +763,7 @@ and
 .I expression2
 are true.
 .TP
-.if t \fIexpression1\fP \fB\(bv\(bv\fP \fIexpression2\fP
-.if n \fIexpression1\fP \fB||\fP \fIexpression2\fP
+\fIexpression1\fP \fB||\fP \fIexpression2\fP
 True if either
 .I expression1
 or
@@ -772,9 +771,7 @@ or
 is true.
 .PD
 .LP
-The \fB&&\fP and
-.if t \fB\(bv\(bv\fP
-.if n \fB||\fP
+The \fB&&\fP and \fB||\fP
 operators do not evaluate \fIexpression2\fP if the value of
 \fIexpression1\fP is sufficient to determine the return value of
 the entire conditional expression.
@@ -5508,10 +5505,14 @@ as if the "!\fIn\fP" history expansion had been specified.
 .B
 yank\-last\-arg (M\-.\^, M\-_\^)
 Insert the last argument to the previous command (the last word of
-the previous history entry).  With an argument,
-behave exactly like \fByank\-nth\-arg\fP.
+the previous history entry).
+With a numeric argument, behave exactly like \fByank\-nth\-arg\fP.
 Successive calls to \fByank\-last\-arg\fP move back through the history
-list, inserting the last argument of each line in turn.
+list, inserting the last word (or the word specified by the argument to
+the first call) of each line in turn.
+Any numeric argument supplied to these successive calls determines
+the direction to move through the history.  A negative argument switches
+the direction through the history (back or forward).
 The history expansion facilities are used to extract the last argument,
 as if the "!$" history expansion had been specified.
 .TP
@@ -8476,8 +8477,8 @@ or
 reserved words, part of any command executed in a
 .B &&
 or
-.B \(bv\(bv
-list except the command following the final \fB&&\fP or \fB\(bv\(bv\fP,
+.B ||
+list except the command following the final \fB&&\fP or \fB||\fP,
 any command in a pipeline but the last,
 or if the command's return value is
 being inverted with
@@ -9262,6 +9263,7 @@ If the first argument is not a valid unary conditional operator, the expression
 is false.
 .TP
 3 arguments
+The following conditions are applied in the order listed.
 If the second argument is one of the binary conditional operators listed above
 under
 .SM
@@ -9394,7 +9396,7 @@ part of the test in an
 statement, part of a command executed in a
 .B &&
 or
-.B \(bv\(bv
+.B ||
 list, or if the command's return value is
 being inverted via
 .BR ! .
index ad79859255d36d09f73d91689b9f00f66316172a..2a47a3daf87e50d3c21315172c778a47c80031eb 100644 (file)
@@ -5508,10 +5508,14 @@ as if the "!\fIn\fP" history expansion had been specified.
 .B
 yank\-last\-arg (M\-.\^, M\-_\^)
 Insert the last argument to the previous command (the last word of
-the previous history entry).  With an argument,
-behave exactly like \fByank\-nth\-arg\fP.
+the previous history entry).
+With a numeric argument, behave exactly like \fByank\-nth\-arg\fP.
 Successive calls to \fByank\-last\-arg\fP move back through the history
-list, inserting the last argument of each line in turn.
+list, inserting the last word (or the word specified by the argument to
+the first call) of each line in turn.
+Any numeric argument supplied to these successive calls determines
+the direction to move through the history.  A negative argument switches
+the direction through the history (back or forward).
 The history expansion facilities are used to extract the last argument,
 as if the "!$" history expansion had been specified.
 .TP
@@ -9210,6 +9214,7 @@ an argument of \fB\-\-\fP as signifying the end of options.
 Expressions may be combined using the following operators, listed
 in decreasing order of precedence.
 The evaluation depends on the number of arguments; see below.
+Operator precedence is used when there are five or more arguments.
 .RS
 .PD 0
 .TP
@@ -9366,8 +9371,8 @@ is
 .BR RETURN ,
 the command
 .I arg
-is executed each time a shell function or a script executed with the
-\fB.\fP or \fBsource\fP builtins finishes executing.
+is executed each time a shell function or a script executed with
+the \fB.\fP or \fBsource\fP builtins finishes executing.
 .if t .sp 0.5
 .if n .sp 1
 If a
index c3d2182034089f9dc6568feae056a52d5bab6faa..9063e59e783a94457e7807e86ca0a2d75236d7d2 100644 (file)
@@ -3239,6 +3239,7 @@ If the first argument is not a valid unary operator, the expression is
 false.
 
 @item 3 arguments
+The following conditions are applied in the order listed.
 If the second argument is one of the binary conditional
 operators (@pxref{Bash Conditional Expressions}), the
 result of the expression is the result of the binary test using the
index 2f457515b2d91844bfe508b82615c43fc5593e13..c3d2182034089f9dc6568feae056a52d5bab6faa 100644 (file)
@@ -1717,7 +1717,7 @@ Bash uses the value of the variable formed from the rest of
 expanded and that value is used in the rest of the substitution, rather
 than the value of @var{parameter} itself.
 This is known as @code{indirect expansion}.
-The exceptions to this are the expansions of $@{!@var{prefix}@*}
+The exceptions to this are the expansions of $@{!@var{prefix}@*@}
 and $@{!@var{name}[@@]@}
 described below.
 The exclamation point must immediately follow the left brace in order to
@@ -3202,6 +3202,7 @@ be a @code{]}.
 Expressions may be combined using the following operators, listed in
 decreasing order of precedence.
 The evaluation depends on the number of arguments; see below.
+Operator precedence is used when there are five or more arguments.
 
 @table @code
 @item ! @var{expr}
@@ -6694,14 +6695,6 @@ the @sc{posix} standard, and include things like passing incorrect options,
 redirection errors, variable assignment errors for assignments preceding
 the command name, and so on.
 
-@item
-If @env{CDPATH} is set, the @code{cd} builtin will not implicitly
-append the current directory to it.  This means that @code{cd} will
-fail if no valid directory name can be constructed from
-any of the entries in @env{$CDPATH}, even if the a directory with
-the same name as the name given as an argument to @code{cd} exists
-in the current directory.
-
 @item
 A non-interactive shell exits with an error status if a variable
 assignment error occurs when no command name follows the assignment
@@ -6709,6 +6702,11 @@ statements.
 A variable assignment error occurs, for example, when trying to assign
 a value to a readonly variable.
 
+@item
+A non-interactive shell exists with an error status if a variable
+assignment error occurs in an assignment statement preceding a special
+builtin, but not with any other simple command.
+
 @item
 A non-interactive shell exits with an error status if the iteration
 variable in a @code{for} statement or the selection variable in a
@@ -6774,10 +6772,6 @@ constructed from @code{$PWD} and the directory name supplied as an argument
 does not refer to an existing directory, @code{cd} will fail instead of
 falling back to @var{physical} mode.
 
-@item
-When the @code{pwd} builtin is supplied the @option{-P} option, it resets
-@code{$PWD} to a pathname containing no symlinks.
-
 @item
 The @code{pwd} builtin verifies that the value it prints is the same as the
 current directory, even if it is not asked to check the file system with the
index 51703009e4c2facaa7d5814ad0af68c51817208b..09244fa443aa1091380b3a2ec6362a3d8bc9be7a 100644 (file)
--- a/externs.h
+++ b/externs.h
@@ -303,6 +303,10 @@ extern int sh_regmatch __P((const char *, const char *, int));
 #define SHMAT_SUBEXP           0x001   /* save subexpressions in SH_REMATCH */
 #define SHMAT_PWARN            0x002   /* print a warning message on invalid regexp */
 
+/* declarations for functions defined in lib/sh/shmbchar.c */
+extern size_t mbstrlen __P((const char *));
+extern char *mbsmbchar __P((const char *));
+
 /* declarations for functions defined in lib/sh/shquote.c */
 extern char *sh_single_quote __P((const char *));
 extern char *sh_double_quote __P((const char *));
index 1853c39d05e0da7c0a2be2352a3758cc923e08a3..4c9036dab891b581b26465fcb3013c41d01e37ba 100644 (file)
@@ -303,9 +303,12 @@ extern int sh_regmatch __P((const char *, const char *, int));
 #define SHMAT_SUBEXP           0x001   /* save subexpressions in SH_REMATCH */
 #define SHMAT_PWARN            0x002   /* print a warning message on invalid regexp */
 
+/* declarations for functions defined in lib/sh/shmbchar.c */
+extern size_t mbstrlen __P((const char *));
+
 /* declarations for functions defined in lib/sh/shquote.c */
-extern char *sh_single_quote __P((char *));
-extern char *sh_double_quote __P((char *));
+extern char *sh_single_quote __P((const char *));
+extern char *sh_double_quote __P((const char *));
 extern char *sh_mkdoublequoted __P((const char *, int, int));
 extern char *sh_un_double_quote __P((char *));
 extern char *sh_backslash_quote __P((char *));
index 1ec0d39aa210d38798616434f3bf77c2a6298789..77f5c6f453218fd07455dee131c479aa0d9e02f2 100644 (file)
--- a/general.h
+++ b/general.h
@@ -216,8 +216,8 @@ typedef void sh_resetsig_func_t __P((int)); /* sh_vintfunc_t */
 
 typedef int sh_ignore_func_t __P((const char *));      /* sh_icpfunc_t */
 
-typedef int sh_assign_func_t __P((const char *));      /* sh_icpfunc_t */
-typedef int sh_wassign_func_t __P((WORD_DESC *));
+typedef int sh_assign_func_t __P((const char *));
+typedef int sh_wassign_func_t __P((WORD_DESC *, int));
 
 typedef int sh_builtin_func_t __P((WORD_LIST *)); /* sh_wlist_func_t */
 
diff --git a/general.h~ b/general.h~
new file mode 100644 (file)
index 0000000..094b449
--- /dev/null
@@ -0,0 +1,325 @@
+/* general.h -- defines that everybody likes to use. */
+
+/* Copyright (C) 1993-2009 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#if !defined (_GENERAL_H_)
+#define _GENERAL_H_
+
+#include "stdc.h"
+
+#include "bashtypes.h"
+#include "chartypes.h"
+
+#if defined (HAVE_SYS_RESOURCE_H) && defined (RLIMTYPE)
+#  if defined (HAVE_SYS_TIME_H)
+#    include <sys/time.h>
+#  endif
+#  include <sys/resource.h>
+#endif
+
+#if defined (HAVE_STRING_H)
+#  include <string.h>
+#else
+#  include <strings.h>
+#endif /* !HAVE_STRING_H */
+
+#if defined (HAVE_LIMITS_H)
+#  include <limits.h>
+#endif
+
+#include "xmalloc.h"
+
+/* NULL pointer type. */
+#if !defined (NULL)
+#  if defined (__STDC__)
+#    define NULL ((void *) 0)
+#  else
+#    define NULL 0x0
+#  endif /* !__STDC__ */
+#endif /* !NULL */
+
+/* Hardly used anymore */
+#define pointer_to_int(x)      (int)((char *)x - (char *)0)
+
+#if defined (alpha) && defined (__GNUC__) && !defined (strchr) && !defined (__STDC__)
+extern char *strchr (), *strrchr ();
+#endif
+
+#if !defined (strcpy) && (defined (HAVE_DECL_STRCPY) && !HAVE_DECL_STRCPY)
+extern char *strcpy __P((char *, const char *));
+#endif
+
+#if !defined (savestring)
+#  define savestring(x) (char *)strcpy (xmalloc (1 + strlen (x)), (x))
+#endif
+
+#ifndef member
+#  define member(c, s) ((c) ? ((char *)mbschr ((s), (c)) != (char *)NULL) : 0)
+#endif
+
+#ifndef whitespace
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+#endif
+
+#ifndef CHAR_MAX
+#  ifdef __CHAR_UNSIGNED__
+#    define CHAR_MAX   0xff
+#  else
+#    define CHAR_MAX   0x7f
+#  endif
+#endif
+
+#ifndef CHAR_BIT
+#  define CHAR_BIT 8
+#endif
+
+/* Nonzero if the integer type T is signed.  */
+#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
+
+/* Bound on length of the string representing an integer value of type T.
+   Subtract one for the sign bit if T is signed;
+   302 / 1000 is log10 (2) rounded up;
+   add one for integer division truncation;
+   add one more for a minus sign if t is signed.  */
+#define INT_STRLEN_BOUND(t) \
+  ((sizeof (t) * CHAR_BIT - TYPE_SIGNED (t)) * 302 / 1000 \
+   + 1 + TYPE_SIGNED (t))
+
+
+/* Define exactly what a legal shell identifier consists of. */
+#define legal_variable_starter(c) (ISALPHA(c) || (c == '_'))
+#define legal_variable_char(c) (ISALNUM(c) || c == '_')
+
+/* Definitions used in subst.c and by the `read' builtin for field
+   splitting. */
+#define spctabnl(c)    ((c) == ' ' || (c) == '\t' || (c) == '\n')
+
+/* All structs which contain a `next' field should have that field
+   as the first field in the struct.  This means that functions
+   can be written to handle the general case for linked lists. */
+typedef struct g_list {
+  struct g_list *next;
+} GENERIC_LIST;
+
+/* Here is a generic structure for associating character strings
+   with integers.  It is used in the parser for shell tokenization. */
+typedef struct {
+  char *word;
+  int token;
+} STRING_INT_ALIST;
+
+/* A macro to avoid making an uneccessary function call. */
+#define REVERSE_LIST(list, type) \
+  ((list && list->next) ? (type)list_reverse ((GENERIC_LIST *)list) \
+                       : (type)(list))
+
+#if __GNUC__ > 1
+#  define FASTCOPY(s, d, n)  __builtin_memcpy ((d), (s), (n))
+#else /* !__GNUC__ */
+#  if !defined (HAVE_BCOPY)
+#    if !defined (HAVE_MEMMOVE)
+#      define FASTCOPY(s, d, n)  memcpy ((d), (s), (n))
+#    else
+#      define FASTCOPY(s, d, n)  memmove ((d), (s), (n))
+#    endif /* !HAVE_MEMMOVE */
+#  else /* HAVE_BCOPY */
+#    define FASTCOPY(s, d, n)  bcopy ((s), (d), (n))
+#  endif /* HAVE_BCOPY */
+#endif /* !__GNUC__ */
+
+/* String comparisons that possibly save a function call each. */
+#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0)
+#define STREQN(a, b, n) ((n == 0) ? (1) \
+                                 : ((a)[0] == (b)[0] && strncmp(a, b, n) == 0))
+
+/* More convenience definitions that possibly save system or libc calls. */
+#define STRLEN(s) (((s) && (s)[0]) ? ((s)[1] ? ((s)[2] ? strlen(s) : 2) : 1) : 0)
+#define FREE(s)  do { if (s) free (s); } while (0)
+#define MEMBER(c, s) (((c) && c == (s)[0] && !(s)[1]) || (member(c, s)))
+
+/* A fairly hairy macro to check whether an allocated string has more room,
+   and to resize it using xrealloc if it does not.
+   STR is the string (char *)
+   CIND is the current index into the string (int)
+   ROOM is the amount of additional room we need in the string (int)
+   CSIZE is the currently-allocated size of STR (int)
+   SINCR is how much to increment CSIZE before calling xrealloc (int) */
+
+#define RESIZE_MALLOCED_BUFFER(str, cind, room, csize, sincr) \
+  do { \
+    if ((cind) + (room) >= csize) \
+      { \
+       while ((cind) + (room) >= csize) \
+         csize += (sincr); \
+       str = xrealloc (str, csize); \
+      } \
+  } while (0)
+
+/* Function pointers can be declared as (Function *)foo. */
+#if !defined (_FUNCTION_DEF)
+#  define _FUNCTION_DEF
+typedef int Function ();
+typedef void VFunction ();
+typedef char *CPFunction ();           /* no longer used */
+typedef char **CPPFunction ();         /* no longer used */
+#endif /* _FUNCTION_DEF */
+
+#ifndef SH_FUNCTION_TYPEDEF
+#  define SH_FUNCTION_TYPEDEF
+
+/* Shell function typedefs with prototypes */
+/* `Generic' function pointer typedefs */
+
+typedef int sh_intfunc_t __P((int));
+typedef int sh_ivoidfunc_t __P((void));
+typedef int sh_icpfunc_t __P((char *));
+typedef int sh_icppfunc_t __P((char **));
+typedef int sh_iptrfunc_t __P((PTR_T));
+
+typedef void sh_voidfunc_t __P((void));
+typedef void sh_vintfunc_t __P((int));
+typedef void sh_vcpfunc_t __P((char *));
+typedef void sh_vcppfunc_t __P((char **));
+typedef void sh_vptrfunc_t __P((PTR_T));
+
+typedef int sh_wdesc_func_t __P((WORD_DESC *));
+typedef int sh_wlist_func_t __P((WORD_LIST *));
+
+typedef int sh_glist_func_t __P((GENERIC_LIST *));
+
+typedef char *sh_string_func_t __P((char *));  /* like savestring, et al. */
+
+typedef int sh_msg_func_t __P((const char *, ...));    /* printf(3)-like */
+typedef void sh_vmsg_func_t __P((const char *, ...));  /* printf(3)-like */
+
+/* Specific function pointer typedefs.  Most of these could be done
+   with #defines. */
+typedef void sh_sv_func_t __P((char *));       /* sh_vcpfunc_t */
+typedef void sh_free_func_t __P((PTR_T));      /* sh_vptrfunc_t */
+typedef void sh_resetsig_func_t __P((int));    /* sh_vintfunc_t */
+
+typedef int sh_ignore_func_t __P((const char *));      /* sh_icpfunc_t */
+
+typedef int sh_assign_func_t __P((const char *, int));
+typedef int sh_wassign_func_t __P((WORD_DESC *));
+
+typedef int sh_builtin_func_t __P((WORD_LIST *)); /* sh_wlist_func_t */
+
+#endif /* SH_FUNCTION_TYPEDEF */
+
+#define NOW    ((time_t) time ((time_t *) 0))
+
+/* Some defines for calling file status functions. */
+#define FS_EXISTS        0x1
+#define FS_EXECABLE      0x2
+#define FS_EXEC_PREFERRED 0x4
+#define FS_EXEC_ONLY     0x8
+#define FS_DIRECTORY     0x10
+#define FS_NODIRS        0x20
+#define FS_READABLE      0x40
+
+/* Default maximum for move_to_high_fd */
+#define HIGH_FD_MAX    256
+
+/* The type of function passed as the fourth argument to qsort(3). */
+#ifdef __STDC__
+typedef int QSFUNC (const void *, const void *);
+#else
+typedef int QSFUNC ();
+#endif 
+
+/* Some useful definitions for Unix pathnames.  Argument convention:
+   x == string, c == character */
+
+#if !defined (__CYGWIN__)
+#  define ABSPATH(x)   ((x)[0] == '/')
+#  define RELPATH(x)   ((x)[0] != '/')
+#else /* __CYGWIN__ */
+#  define ABSPATH(x)   (((x)[0] && ISALPHA((unsigned char)(x)[0]) && (x)[1] == ':') || ISDIRSEP((x)[0]))
+#  define RELPATH(x)   (ABSPATH(x) == 0)
+#endif /* __CYGWIN__ */
+
+#define ROOTEDPATH(x)  (ABSPATH(x))
+
+#define DIRSEP '/'
+#if !defined (__CYGWIN__)
+#  define ISDIRSEP(c)  ((c) == '/')
+#else
+#  define ISDIRSEP(c)  ((c) == '/' || (c) == '\\')
+#endif /* __CYGWIN__ */
+#define PATHSEP(c)     (ISDIRSEP(c) || (c) == 0)
+
+#if 0
+/* Declarations for functions defined in xmalloc.c */
+extern PTR_T xmalloc __P((size_t));
+extern PTR_T xrealloc __P((void *, size_t));
+extern void xfree __P((void *));
+#endif
+
+/* Declarations for functions defined in general.c */
+extern void posix_initialize __P((int));
+
+#if defined (RLIMTYPE)
+extern RLIMTYPE string_to_rlimtype __P((char *));
+extern void print_rlimtype __P((RLIMTYPE, int));
+#endif
+
+extern int all_digits __P((char *));
+extern int legal_number __P((const char *, intmax_t *));
+extern int legal_identifier __P((char *));
+extern int check_identifier __P((WORD_DESC *, int));
+extern int legal_alias_name __P((char *, int));
+extern int assignment __P((const char *, int));
+
+extern int sh_unset_nodelay_mode __P((int));
+extern int sh_validfd __P((int));
+extern void check_dev_tty __P((void));
+extern int move_to_high_fd __P((int, int, int));
+extern int check_binary_file __P((char *, int));
+
+#ifdef _POSIXSTAT_H_
+extern int same_file __P((char *, char *, struct stat *, struct stat *));
+#endif
+
+extern int sh_openpipe __P((int *));
+extern int sh_closepipe __P((int *));
+
+extern int file_exists __P((char *));
+extern int file_isdir __P((char  *));
+extern int file_iswdir __P((char  *));
+extern int absolute_pathname __P((const char *));
+extern int absolute_program __P((const char *));
+
+extern char *make_absolute __P((char *, char *));
+extern char *base_pathname __P((char *));
+extern char *full_pathname __P((char *));
+extern char *polite_directory_format __P((char *));
+extern char *trim_pathname __P((char *, int));
+
+extern char *extract_colon_unit __P((char *, int *));
+
+extern void tilde_initialize __P((void));
+extern char *bash_tilde_find_word __P((const char *, int, int *));
+extern char *bash_tilde_expand __P((const char *, int));
+
+extern int group_member __P((gid_t));
+extern char **get_group_list __P((int *));
+extern int *get_group_array __P((int *));
+
+#endif /* _GENERAL_H_ */
diff --git a/include/shmbchar.h b/include/shmbchar.h
new file mode 100644 (file)
index 0000000..8e8bc10
--- /dev/null
@@ -0,0 +1,350 @@
+/* Multibyte character data type.
+   Copyright (C) 2001, 2005-2007, 2009-2010 Free Software Foundation, Inc.
+
+   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 3 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, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>.  */
+
+/* A multibyte character is a short subsequence of a char* string,
+   representing a single wide character.
+
+   We use multibyte characters instead of wide characters because of
+   the following goals:
+   1) correct multibyte handling, i.e. operate according to the LC_CTYPE
+      locale,
+   2) ease of maintenance, i.e. the maintainer needs not know all details
+      of the ISO C 99 standard,
+   3) don't fail grossly if the input is not in the encoding set by the
+      locale, because often different encodings are in use in the same
+      countries (ISO-8859-1/UTF-8, EUC-JP/Shift_JIS, ...),
+   4) fast in the case of ASCII characters,
+   5) portability, i.e. don't make unportable assumptions about wchar_t.
+
+   Multibyte characters are only accessed through the mb* macros.
+
+   mb_ptr (mbc)
+     return a pointer to the beginning of the multibyte sequence.
+
+   mb_len (mbc)
+     returns the number of bytes occupied by the multibyte sequence.
+     Always > 0.
+
+   mb_iseq (mbc, sc)
+     returns true if mbc is the standard ASCII character sc.
+
+   mb_isnul (mbc)
+     returns true if mbc is the nul character.
+
+   mb_cmp (mbc1, mbc2)
+     returns a positive, zero, or negative value depending on whether mbc1
+     sorts after, same or before mbc2.
+
+   mb_casecmp (mbc1, mbc2)
+     returns a positive, zero, or negative value depending on whether mbc1
+     sorts after, same or before mbc2, modulo upper/lowercase conversion.
+
+   mb_equal (mbc1, mbc2)
+     returns true if mbc1 and mbc2 are equal.
+
+   mb_caseequal (mbc1, mbc2)
+     returns true if mbc1 and mbc2 are equal modulo upper/lowercase conversion.
+
+   mb_isalnum (mbc)
+     returns true if mbc is alphanumeric.
+
+   mb_isalpha (mbc)
+     returns true if mbc is alphabetic.
+
+   mb_isascii(mbc)
+     returns true if mbc is plain ASCII.
+
+   mb_isblank (mbc)
+     returns true if mbc is a blank.
+
+   mb_iscntrl (mbc)
+     returns true if mbc is a control character.
+
+   mb_isdigit (mbc)
+     returns true if mbc is a decimal digit.
+
+   mb_isgraph (mbc)
+     returns true if mbc is a graphic character.
+
+   mb_islower (mbc)
+     returns true if mbc is lowercase.
+
+   mb_isprint (mbc)
+     returns true if mbc is a printable character.
+
+   mb_ispunct (mbc)
+     returns true if mbc is a punctuation character.
+
+   mb_isspace (mbc)
+     returns true if mbc is a space character.
+
+   mb_isupper (mbc)
+     returns true if mbc is uppercase.
+
+   mb_isxdigit (mbc)
+     returns true if mbc is a hexadecimal digit.
+
+   mb_width (mbc)
+     returns the number of columns on the output device occupied by mbc.
+     Always >= 0.
+
+   mb_putc (mbc, stream)
+     outputs mbc on stream, a byte oriented FILE stream opened for output.
+
+   mb_setascii (&mbc, sc)
+     assigns the standard ASCII character sc to mbc.
+
+   mb_copy (&destmbc, &srcmbc)
+     copies srcmbc to destmbc.
+
+   Here are the function prototypes of the macros.
+
+   typedef int bool;
+   extern const char *  mb_ptr (const mbchar_t mbc);
+   extern size_t        mb_len (const mbchar_t mbc);
+   extern bool          mb_iseq (const mbchar_t mbc, char sc);
+   extern bool          mb_isnul (const mbchar_t mbc);
+   extern int           mb_cmp (const mbchar_t mbc1, const mbchar_t mbc2);
+   extern int           mb_casecmp (const mbchar_t mbc1, const mbchar_t mbc2);
+   extern bool          mb_equal (const mbchar_t mbc1, const mbchar_t mbc2);
+   extern bool          mb_caseequal (const mbchar_t mbc1, const mbchar_t mbc2);
+   extern bool          mb_isalnum (const mbchar_t mbc);
+   extern bool          mb_isalpha (const mbchar_t mbc);
+   extern bool          mb_isascii (const mbchar_t mbc);
+   extern bool          mb_isblank (const mbchar_t mbc);
+   extern bool          mb_iscntrl (const mbchar_t mbc);
+   extern bool          mb_isdigit (const mbchar_t mbc);
+   extern bool          mb_isgraph (const mbchar_t mbc);
+   extern bool          mb_islower (const mbchar_t mbc);
+   extern bool          mb_isprint (const mbchar_t mbc);
+   extern bool          mb_ispunct (const mbchar_t mbc);
+   extern bool          mb_isspace (const mbchar_t mbc);
+   extern bool          mb_isupper (const mbchar_t mbc);
+   extern bool          mb_isxdigit (const mbchar_t mbc);
+   extern int           mb_width (const mbchar_t mbc);
+   extern void          mb_putc (const mbchar_t mbc, FILE *stream);
+   extern void          mb_setascii (mbchar_t *new, char sc);
+   extern void          mb_copy (mbchar_t *new, const mbchar_t *old);
+ */
+
+#ifndef _SHMBCHAR_H
+#define _SHMBCHAR_H 1
+
+#include <string.h>
+
+/* Tru64 with Desktop Toolkit C has a bug: <stdio.h> must be included before
+   <wchar.h>.
+   BSD/OS 4.1 has a bug: <stdio.h> and <time.h> must be included before
+   <wchar.h>.  */
+#include <stdio.h>
+#include <time.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#define MBCHAR_BUF_SIZE 24
+
+struct mbchar
+{
+  const char *ptr;      /* pointer to current character */
+  size_t bytes;         /* number of bytes of current character, > 0 */
+  int wc_valid;        /* true if wc is a valid wide character */
+  wchar_t wc;           /* if wc_valid: the current character */
+  char buf[MBCHAR_BUF_SIZE]; /* room for the bytes, used for file input only */
+};
+
+/* EOF (not a real character) is represented with bytes = 0 and
+   wc_valid = false.  */
+
+typedef struct mbchar mbchar_t;
+
+/* Access the current character.  */
+#define mb_ptr(mbc) ((mbc).ptr)
+#define mb_len(mbc) ((mbc).bytes)
+
+/* Comparison of characters.  */
+#define mb_iseq(mbc, sc) ((mbc).wc_valid && (mbc).wc == (sc))
+#define mb_isnul(mbc) ((mbc).wc_valid && (mbc).wc == 0)
+#define mb_cmp(mbc1, mbc2) \
+  ((mbc1).wc_valid                                                      \
+   ? ((mbc2).wc_valid                                                   \
+      ? (int) (mbc1).wc - (int) (mbc2).wc                               \
+      : -1)                                                             \
+   : ((mbc2).wc_valid                                                   \
+      ? 1                                                               \
+      : (mbc1).bytes == (mbc2).bytes                                    \
+        ? memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes)                 \
+        : (mbc1).bytes < (mbc2).bytes                                   \
+          ? (memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes) > 0 ? 1 : -1) \
+          : (memcmp ((mbc1).ptr, (mbc2).ptr, (mbc2).bytes) >= 0 ? 1 : -1)))
+#define mb_casecmp(mbc1, mbc2) \
+  ((mbc1).wc_valid                                                      \
+   ? ((mbc2).wc_valid                                                   \
+      ? (int) towlower ((mbc1).wc) - (int) towlower ((mbc2).wc)         \
+      : -1)                                                             \
+   : ((mbc2).wc_valid                                                   \
+      ? 1                                                               \
+      : (mbc1).bytes == (mbc2).bytes                                    \
+        ? memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes)                 \
+        : (mbc1).bytes < (mbc2).bytes                                   \
+          ? (memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes) > 0 ? 1 : -1) \
+          : (memcmp ((mbc1).ptr, (mbc2).ptr, (mbc2).bytes) >= 0 ? 1 : -1)))
+#define mb_equal(mbc1, mbc2) \
+  ((mbc1).wc_valid && (mbc2).wc_valid                                   \
+   ? (mbc1).wc == (mbc2).wc                                             \
+   : (mbc1).bytes == (mbc2).bytes                                       \
+     && memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes) == 0)
+#define mb_caseequal(mbc1, mbc2) \
+  ((mbc1).wc_valid && (mbc2).wc_valid                                   \
+   ? towlower ((mbc1).wc) == towlower ((mbc2).wc)                       \
+   : (mbc1).bytes == (mbc2).bytes                                       \
+     && memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes) == 0)
+
+/* <ctype.h>, <wctype.h> classification.  */
+#define mb_isascii(mbc) \
+  ((mbc).wc_valid && (mbc).wc >= 0 && (mbc).wc <= 127)
+#define mb_isalnum(mbc) ((mbc).wc_valid && iswalnum ((mbc).wc))
+#define mb_isalpha(mbc) ((mbc).wc_valid && iswalpha ((mbc).wc))
+#define mb_isblank(mbc) ((mbc).wc_valid && iswblank ((mbc).wc))
+#define mb_iscntrl(mbc) ((mbc).wc_valid && iswcntrl ((mbc).wc))
+#define mb_isdigit(mbc) ((mbc).wc_valid && iswdigit ((mbc).wc))
+#define mb_isgraph(mbc) ((mbc).wc_valid && iswgraph ((mbc).wc))
+#define mb_islower(mbc) ((mbc).wc_valid && iswlower ((mbc).wc))
+#define mb_isprint(mbc) ((mbc).wc_valid && iswprint ((mbc).wc))
+#define mb_ispunct(mbc) ((mbc).wc_valid && iswpunct ((mbc).wc))
+#define mb_isspace(mbc) ((mbc).wc_valid && iswspace ((mbc).wc))
+#define mb_isupper(mbc) ((mbc).wc_valid && iswupper ((mbc).wc))
+#define mb_isxdigit(mbc) ((mbc).wc_valid && iswxdigit ((mbc).wc))
+
+/* Extra <wchar.h> function.  */
+
+/* Unprintable characters appear as a small box of width 1.  */
+#define MB_UNPRINTABLE_WIDTH 1
+
+static inline int
+mb_width_aux (wint_t wc)
+{
+  int w = wcwidth (wc);
+  /* For unprintable characters, arbitrarily return 0 for control characters
+     and MB_UNPRINTABLE_WIDTH otherwise.  */
+  return (w >= 0 ? w : iswcntrl (wc) ? 0 : MB_UNPRINTABLE_WIDTH);
+}
+
+#define mb_width(mbc) \
+  ((mbc).wc_valid ? mb_width_aux ((mbc).wc) : MB_UNPRINTABLE_WIDTH)
+
+/* Output.  */
+#define mb_putc(mbc, stream)  fwrite ((mbc).ptr, 1, (mbc).bytes, (stream))
+
+/* Assignment.  */
+#define mb_setascii(mbc, sc) \
+  ((mbc)->ptr = (mbc)->buf, (mbc)->bytes = 1, (mbc)->wc_valid = 1, \
+   (mbc)->wc = (mbc)->buf[0] = (sc))
+
+/* Copying a character.  */
+static inline void
+mb_copy (mbchar_t *new_mbc, const mbchar_t *old_mbc)
+{
+  if (old_mbc->ptr == &old_mbc->buf[0])
+    {
+      memcpy (&new_mbc->buf[0], &old_mbc->buf[0], old_mbc->bytes);
+      new_mbc->ptr = &new_mbc->buf[0];
+    }
+  else
+    new_mbc->ptr = old_mbc->ptr;
+  new_mbc->bytes = old_mbc->bytes;
+  if ((new_mbc->wc_valid = old_mbc->wc_valid))
+    new_mbc->wc = old_mbc->wc;
+}
+
+
+/* is_basic(c) tests whether the single-byte character c is in the
+   ISO C "basic character set".
+   This is a convenience function, and is in this file only to share code
+   between mbiter_multi.h and mbfile_multi.h.  */
+#if (' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+    && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+    && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+    && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+    && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+    && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+    && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+    && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+    && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+    && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+    && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+    && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+    && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+    && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+    && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+    && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+    && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+    && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+    && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+    && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+    && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+    && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+    && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)
+/* The character set is ISO-646, not EBCDIC. */
+# define IS_BASIC_ASCII 1
+
+extern const unsigned int is_basic_table[];
+
+static inline int
+is_basic (char c)
+{
+  return (is_basic_table [(unsigned char) c >> 5] >> ((unsigned char) c & 31))
+         & 1;
+}
+
+#else
+
+static inline int
+is_basic (char c)
+{
+  switch (c)
+    {
+    case '\t': case '\v': case '\f':
+    case ' ': case '!': case '"': case '#': case '%':
+    case '&': case '\'': case '(': case ')': case '*':
+    case '+': case ',': case '-': case '.': case '/':
+    case '0': case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+    case ':': case ';': case '<': case '=': case '>':
+    case '?':
+    case 'A': case 'B': case 'C': case 'D': case 'E':
+    case 'F': case 'G': case 'H': case 'I': case 'J':
+    case 'K': case 'L': case 'M': case 'N': case 'O':
+    case 'P': case 'Q': case 'R': case 'S': case 'T':
+    case 'U': case 'V': case 'W': case 'X': case 'Y':
+    case 'Z':
+    case '[': case '\\': case ']': case '^': case '_':
+    case 'a': case 'b': case 'c': case 'd': case 'e':
+    case 'f': case 'g': case 'h': case 'i': case 'j':
+    case 'k': case 'l': case 'm': case 'n': case 'o':
+    case 'p': case 'q': case 'r': case 's': case 't':
+    case 'u': case 'v': case 'w': case 'x': case 'y':
+    case 'z': case '{': case '|': case '}': case '~':
+      return 1;
+    default:
+      return 0;
+    }
+}
+
+#endif
+
+#endif /* _SHMBCHAR_H */
diff --git a/include/shmbchar.h~ b/include/shmbchar.h~
new file mode 100644 (file)
index 0000000..012bfcb
--- /dev/null
@@ -0,0 +1,350 @@
+/* Multibyte character data type.
+   Copyright (C) 2001, 2005-2007, 2009-2010 Free Software Foundation, Inc.
+
+   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 3 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, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <bruno@clisp.org>.  */
+
+/* A multibyte character is a short subsequence of a char* string,
+   representing a single wide character.
+
+   We use multibyte characters instead of wide characters because of
+   the following goals:
+   1) correct multibyte handling, i.e. operate according to the LC_CTYPE
+      locale,
+   2) ease of maintenance, i.e. the maintainer needs not know all details
+      of the ISO C 99 standard,
+   3) don't fail grossly if the input is not in the encoding set by the
+      locale, because often different encodings are in use in the same
+      countries (ISO-8859-1/UTF-8, EUC-JP/Shift_JIS, ...),
+   4) fast in the case of ASCII characters,
+   5) portability, i.e. don't make unportable assumptions about wchar_t.
+
+   Multibyte characters are only accessed through the mb* macros.
+
+   mb_ptr (mbc)
+     return a pointer to the beginning of the multibyte sequence.
+
+   mb_len (mbc)
+     returns the number of bytes occupied by the multibyte sequence.
+     Always > 0.
+
+   mb_iseq (mbc, sc)
+     returns true if mbc is the standard ASCII character sc.
+
+   mb_isnul (mbc)
+     returns true if mbc is the nul character.
+
+   mb_cmp (mbc1, mbc2)
+     returns a positive, zero, or negative value depending on whether mbc1
+     sorts after, same or before mbc2.
+
+   mb_casecmp (mbc1, mbc2)
+     returns a positive, zero, or negative value depending on whether mbc1
+     sorts after, same or before mbc2, modulo upper/lowercase conversion.
+
+   mb_equal (mbc1, mbc2)
+     returns true if mbc1 and mbc2 are equal.
+
+   mb_caseequal (mbc1, mbc2)
+     returns true if mbc1 and mbc2 are equal modulo upper/lowercase conversion.
+
+   mb_isalnum (mbc)
+     returns true if mbc is alphanumeric.
+
+   mb_isalpha (mbc)
+     returns true if mbc is alphabetic.
+
+   mb_isascii(mbc)
+     returns true if mbc is plain ASCII.
+
+   mb_isblank (mbc)
+     returns true if mbc is a blank.
+
+   mb_iscntrl (mbc)
+     returns true if mbc is a control character.
+
+   mb_isdigit (mbc)
+     returns true if mbc is a decimal digit.
+
+   mb_isgraph (mbc)
+     returns true if mbc is a graphic character.
+
+   mb_islower (mbc)
+     returns true if mbc is lowercase.
+
+   mb_isprint (mbc)
+     returns true if mbc is a printable character.
+
+   mb_ispunct (mbc)
+     returns true if mbc is a punctuation character.
+
+   mb_isspace (mbc)
+     returns true if mbc is a space character.
+
+   mb_isupper (mbc)
+     returns true if mbc is uppercase.
+
+   mb_isxdigit (mbc)
+     returns true if mbc is a hexadecimal digit.
+
+   mb_width (mbc)
+     returns the number of columns on the output device occupied by mbc.
+     Always >= 0.
+
+   mb_putc (mbc, stream)
+     outputs mbc on stream, a byte oriented FILE stream opened for output.
+
+   mb_setascii (&mbc, sc)
+     assigns the standard ASCII character sc to mbc.
+
+   mb_copy (&destmbc, &srcmbc)
+     copies srcmbc to destmbc.
+
+   Here are the function prototypes of the macros.
+
+   extern const char *  mb_ptr (const mbchar_t mbc);
+   extern size_t        mb_len (const mbchar_t mbc);
+   extern bool          mb_iseq (const mbchar_t mbc, char sc);
+   extern bool          mb_isnul (const mbchar_t mbc);
+   extern int           mb_cmp (const mbchar_t mbc1, const mbchar_t mbc2);
+   extern int           mb_casecmp (const mbchar_t mbc1, const mbchar_t mbc2);
+   extern bool          mb_equal (const mbchar_t mbc1, const mbchar_t mbc2);
+   extern bool          mb_caseequal (const mbchar_t mbc1, const mbchar_t mbc2);
+   extern bool          mb_isalnum (const mbchar_t mbc);
+   extern bool          mb_isalpha (const mbchar_t mbc);
+   extern bool          mb_isascii (const mbchar_t mbc);
+   extern bool          mb_isblank (const mbchar_t mbc);
+   extern bool          mb_iscntrl (const mbchar_t mbc);
+   extern bool          mb_isdigit (const mbchar_t mbc);
+   extern bool          mb_isgraph (const mbchar_t mbc);
+   extern bool          mb_islower (const mbchar_t mbc);
+   extern bool          mb_isprint (const mbchar_t mbc);
+   extern bool          mb_ispunct (const mbchar_t mbc);
+   extern bool          mb_isspace (const mbchar_t mbc);
+   extern bool          mb_isupper (const mbchar_t mbc);
+   extern bool          mb_isxdigit (const mbchar_t mbc);
+   extern int           mb_width (const mbchar_t mbc);
+   extern void          mb_putc (const mbchar_t mbc, FILE *stream);
+   extern void          mb_setascii (mbchar_t *new, char sc);
+   extern void          mb_copy (mbchar_t *new, const mbchar_t *old);
+ */
+
+#ifndef _MBCHAR_H
+#define _MBCHAR_H 1
+
+#include <stdbool.h>
+#include <string.h>
+
+/* Tru64 with Desktop Toolkit C has a bug: <stdio.h> must be included before
+   <wchar.h>.
+   BSD/OS 4.1 has a bug: <stdio.h> and <time.h> must be included before
+   <wchar.h>.  */
+#include <stdio.h>
+#include <time.h>
+#include <wchar.h>
+#include <wctype.h>
+
+#define MBCHAR_BUF_SIZE 24
+
+struct mbchar
+{
+  const char *ptr;      /* pointer to current character */
+  size_t bytes;         /* number of bytes of current character, > 0 */
+  bool wc_valid;        /* true if wc is a valid wide character */
+  wchar_t wc;           /* if wc_valid: the current character */
+  char buf[MBCHAR_BUF_SIZE]; /* room for the bytes, used for file input only */
+};
+
+/* EOF (not a real character) is represented with bytes = 0 and
+   wc_valid = false.  */
+
+typedef struct mbchar mbchar_t;
+
+/* Access the current character.  */
+#define mb_ptr(mbc) ((mbc).ptr)
+#define mb_len(mbc) ((mbc).bytes)
+
+/* Comparison of characters.  */
+#define mb_iseq(mbc, sc) ((mbc).wc_valid && (mbc).wc == (sc))
+#define mb_isnul(mbc) ((mbc).wc_valid && (mbc).wc == 0)
+#define mb_cmp(mbc1, mbc2) \
+  ((mbc1).wc_valid                                                      \
+   ? ((mbc2).wc_valid                                                   \
+      ? (int) (mbc1).wc - (int) (mbc2).wc                               \
+      : -1)                                                             \
+   : ((mbc2).wc_valid                                                   \
+      ? 1                                                               \
+      : (mbc1).bytes == (mbc2).bytes                                    \
+        ? memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes)                 \
+        : (mbc1).bytes < (mbc2).bytes                                   \
+          ? (memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes) > 0 ? 1 : -1) \
+          : (memcmp ((mbc1).ptr, (mbc2).ptr, (mbc2).bytes) >= 0 ? 1 : -1)))
+#define mb_casecmp(mbc1, mbc2) \
+  ((mbc1).wc_valid                                                      \
+   ? ((mbc2).wc_valid                                                   \
+      ? (int) towlower ((mbc1).wc) - (int) towlower ((mbc2).wc)         \
+      : -1)                                                             \
+   : ((mbc2).wc_valid                                                   \
+      ? 1                                                               \
+      : (mbc1).bytes == (mbc2).bytes                                    \
+        ? memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes)                 \
+        : (mbc1).bytes < (mbc2).bytes                                   \
+          ? (memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes) > 0 ? 1 : -1) \
+          : (memcmp ((mbc1).ptr, (mbc2).ptr, (mbc2).bytes) >= 0 ? 1 : -1)))
+#define mb_equal(mbc1, mbc2) \
+  ((mbc1).wc_valid && (mbc2).wc_valid                                   \
+   ? (mbc1).wc == (mbc2).wc                                             \
+   : (mbc1).bytes == (mbc2).bytes                                       \
+     && memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes) == 0)
+#define mb_caseequal(mbc1, mbc2) \
+  ((mbc1).wc_valid && (mbc2).wc_valid                                   \
+   ? towlower ((mbc1).wc) == towlower ((mbc2).wc)                       \
+   : (mbc1).bytes == (mbc2).bytes                                       \
+     && memcmp ((mbc1).ptr, (mbc2).ptr, (mbc1).bytes) == 0)
+
+/* <ctype.h>, <wctype.h> classification.  */
+#define mb_isascii(mbc) \
+  ((mbc).wc_valid && (mbc).wc >= 0 && (mbc).wc <= 127)
+#define mb_isalnum(mbc) ((mbc).wc_valid && iswalnum ((mbc).wc))
+#define mb_isalpha(mbc) ((mbc).wc_valid && iswalpha ((mbc).wc))
+#define mb_isblank(mbc) ((mbc).wc_valid && iswblank ((mbc).wc))
+#define mb_iscntrl(mbc) ((mbc).wc_valid && iswcntrl ((mbc).wc))
+#define mb_isdigit(mbc) ((mbc).wc_valid && iswdigit ((mbc).wc))
+#define mb_isgraph(mbc) ((mbc).wc_valid && iswgraph ((mbc).wc))
+#define mb_islower(mbc) ((mbc).wc_valid && iswlower ((mbc).wc))
+#define mb_isprint(mbc) ((mbc).wc_valid && iswprint ((mbc).wc))
+#define mb_ispunct(mbc) ((mbc).wc_valid && iswpunct ((mbc).wc))
+#define mb_isspace(mbc) ((mbc).wc_valid && iswspace ((mbc).wc))
+#define mb_isupper(mbc) ((mbc).wc_valid && iswupper ((mbc).wc))
+#define mb_isxdigit(mbc) ((mbc).wc_valid && iswxdigit ((mbc).wc))
+
+/* Extra <wchar.h> function.  */
+
+/* Unprintable characters appear as a small box of width 1.  */
+#define MB_UNPRINTABLE_WIDTH 1
+
+static inline int
+mb_width_aux (wint_t wc)
+{
+  int w = wcwidth (wc);
+  /* For unprintable characters, arbitrarily return 0 for control characters
+     and MB_UNPRINTABLE_WIDTH otherwise.  */
+  return (w >= 0 ? w : iswcntrl (wc) ? 0 : MB_UNPRINTABLE_WIDTH);
+}
+
+#define mb_width(mbc) \
+  ((mbc).wc_valid ? mb_width_aux ((mbc).wc) : MB_UNPRINTABLE_WIDTH)
+
+/* Output.  */
+#define mb_putc(mbc, stream)  fwrite ((mbc).ptr, 1, (mbc).bytes, (stream))
+
+/* Assignment.  */
+#define mb_setascii(mbc, sc) \
+  ((mbc)->ptr = (mbc)->buf, (mbc)->bytes = 1, (mbc)->wc_valid = 1, \
+   (mbc)->wc = (mbc)->buf[0] = (sc))
+
+/* Copying a character.  */
+static inline void
+mb_copy (mbchar_t *new_mbc, const mbchar_t *old_mbc)
+{
+  if (old_mbc->ptr == &old_mbc->buf[0])
+    {
+      memcpy (&new_mbc->buf[0], &old_mbc->buf[0], old_mbc->bytes);
+      new_mbc->ptr = &new_mbc->buf[0];
+    }
+  else
+    new_mbc->ptr = old_mbc->ptr;
+  new_mbc->bytes = old_mbc->bytes;
+  if ((new_mbc->wc_valid = old_mbc->wc_valid))
+    new_mbc->wc = old_mbc->wc;
+}
+
+
+/* is_basic(c) tests whether the single-byte character c is in the
+   ISO C "basic character set".
+   This is a convenience function, and is in this file only to share code
+   between mbiter_multi.h and mbfile_multi.h.  */
+#if (' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
+    && ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
+    && (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
+    && ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
+    && ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
+    && ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
+    && ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
+    && ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
+    && ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
+    && ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
+    && ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
+    && ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
+    && ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
+    && ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
+    && ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
+    && ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
+    && ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
+    && ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
+    && ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
+    && ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
+    && ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
+    && ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
+    && ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126)
+/* The character set is ISO-646, not EBCDIC. */
+# define IS_BASIC_ASCII 1
+
+extern const unsigned int is_basic_table[];
+
+static inline bool
+is_basic (char c)
+{
+  return (is_basic_table [(unsigned char) c >> 5] >> ((unsigned char) c & 31))
+         & 1;
+}
+
+#else
+
+static inline bool
+is_basic (char c)
+{
+  switch (c)
+    {
+    case '\t': case '\v': case '\f':
+    case ' ': case '!': case '"': case '#': case '%':
+    case '&': case '\'': case '(': case ')': case '*':
+    case '+': case ',': case '-': case '.': case '/':
+    case '0': case '1': case '2': case '3': case '4':
+    case '5': case '6': case '7': case '8': case '9':
+    case ':': case ';': case '<': case '=': case '>':
+    case '?':
+    case 'A': case 'B': case 'C': case 'D': case 'E':
+    case 'F': case 'G': case 'H': case 'I': case 'J':
+    case 'K': case 'L': case 'M': case 'N': case 'O':
+    case 'P': case 'Q': case 'R': case 'S': case 'T':
+    case 'U': case 'V': case 'W': case 'X': case 'Y':
+    case 'Z':
+    case '[': case '\\': case ']': case '^': case '_':
+    case 'a': case 'b': case 'c': case 'd': case 'e':
+    case 'f': case 'g': case 'h': case 'i': case 'j':
+    case 'k': case 'l': case 'm': case 'n': case 'o':
+    case 'p': case 'q': case 'r': case 's': case 't':
+    case 'u': case 'v': case 'w': case 'x': case 'y':
+    case 'z': case '{': case '|': case '}': case '~':
+      return 1;
+    default:
+      return 0;
+    }
+}
+
+#endif
+
+#endif /* _MBCHAR_H */
index 2b4080418ad0f887a8fad2805ca8d892fdfca117..b793fe426ddbb74aabe909a486a2734f590a64b1 100644 (file)
@@ -25,6 +25,7 @@
 
 /* Include config.h for HANDLE_MULTIBYTE */
 #include <config.h>
+#include "shmbchar.h"
 
 #if defined (HANDLE_MULTIBYTE)
 
@@ -101,9 +102,16 @@ extern char *xstrchr __P((const char *, int));
          { \
            mbstate_t state_bak; \
            size_t mblength; \
+           int _f; \
 \
-           state_bak = state; \
-           mblength = mbrlen ((_str) + (_i), (_strsize) - (_i), &state); \
+           _f = is_basic ((_str)[_i]); \
+           if (_f) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_str) + (_i), (_strsize) - (_i), &state); \
+             } \
 \
            if (mblength == (size_t)-2 || mblength == (size_t)-1) \
              { \
@@ -134,9 +142,16 @@ extern char *xstrchr __P((const char *, int));
          { \
            mbstate_t state_bak; \
            size_t mblength; \
+           int _f; \
 \
-           state_bak = state; \
-           mblength = mbrlen ((_str), (_strsize), &state); \
+           _f = is_basic (*(_str)); \
+           if (_f) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_str), (_strsize), &state); \
+             } \
 \
            if (mblength == (size_t)-2 || mblength == (size_t)-1) \
              { \
@@ -247,8 +262,14 @@ extern char *xstrchr __P((const char *, int));
            size_t mblength; \
            int _k; \
 \
-           state_bak = state; \
-           mblength = mbrlen ((_src), (_srcend) - (_src), &state); \
+           _k = is_basic (*(_src)); \
+           if (_k) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_src), (_srcend) - (_src), &state); \
+             } \
            if (mblength == (size_t)-2 || mblength == (size_t)-1) \
              { \
                state = state_bak; \
@@ -280,8 +301,14 @@ extern char *xstrchr __P((const char *, int));
            size_t mblength; \
            int _k; \
 \
-           state_bak = state; \
-           mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src)+(_si)), &state); \
+           _k = is_basic (*((_src) + (_si))); \
+           if (_k) \
+             mblength = 1; \
+           else \
+             {\
+               state_bak = state; \
+               mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src)+(_si)), &state); \
+             } \
            if (mblength == (size_t)-2 || mblength == (size_t)-1) \
              { \
                state = state_bak; \
@@ -317,8 +344,14 @@ extern char *xstrchr __P((const char *, int));
            size_t mblength; \
            int _i; \
 \
-           state_bak = state; \
-           mblength = mbrlen ((_src) + (_si), (_slen) - (_si), &state); \
+           _i = is_basic (*((_src) + (_si))); \
+           if (_i) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_src) + (_si), (_slen) - (_si), &state); \
+             } \
            if (mblength == (size_t)-2 || mblength == (size_t)-1) \
              { \
                state = state_bak; \
@@ -356,9 +389,16 @@ extern char *xstrchr __P((const char *, int));
          { \
            mbstate_t state_bak; \
            size_t mblength; \
+           int _i; \
 \
-           state_bak = state; \
-           mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src) + (_si)), &state); \
+           _i = is_basic (*((_src) + (_si))); \
+           if (_i) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src) + (_si)), &state); \
+             } \
            if (mblength == (size_t)-2 || mblength == (size_t)-1) \
              { \
                state = state_bak; \
@@ -395,8 +435,14 @@ extern char *xstrchr __P((const char *, int));
            mbstate_t state_bak; \
            size_t mblength; \
 \
-           state_bak = state; \
-           mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \
+           i = is_basic (*((_src) + (_si))); \
+           if (i) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \
+             } \
            if (mblength == (size_t)-1 || mblength == (size_t)-2) \
              { \
                state = state_bak; \
@@ -427,8 +473,14 @@ extern char *xstrchr __P((const char *, int));
            mbstate_t state_bak; \
            size_t mblength; \
 \
-           state_bak = state; \
-           mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \
+           i = is_basic (*((_src) + (_si))); \
+           if (i) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \
+             } \
            if (mblength == (size_t)-1 || mblength == (size_t)-2) \
              { \
                state = state_bak; \
diff --git a/include/shmbutil.h~ b/include/shmbutil.h~
new file mode 100644 (file)
index 0000000..ebe22f7
--- /dev/null
@@ -0,0 +1,470 @@
+/* shmbutil.h -- utility functions for multibyte characters. */
+
+/* Copyright (C) 2002-2004 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+                                 
+#if !defined (_SH_MBUTIL_H_)
+#define _SH_MBUTIL_H_
+
+#include "stdc.h"
+
+/* Include config.h for HANDLE_MULTIBYTE */
+#include <config.h>
+#include "shmbchar.h"
+
+#if defined (HANDLE_MULTIBYTE)
+
+extern size_t xmbsrtowcs __P((wchar_t *, const char **, size_t, mbstate_t *));
+extern size_t xdupmbstowcs __P((wchar_t **, char ***, const char *));
+
+extern size_t mbstrlen __P((const char *));
+
+extern char *xstrchr __P((const char *, int));
+
+#ifndef MB_INVALIDCH
+#define MB_INVALIDCH(x)                ((x) == (size_t)-1 || (x) == (size_t)-2)
+#define MB_NULLWCH(x)          ((x) == 0)
+#endif
+
+#define MBSLEN(s)      (((s) && (s)[0]) ? ((s)[1] ? mbstrlen (s) : 1) : 0)
+#define MB_STRLEN(s)   ((MB_CUR_MAX > 1) ? MBSLEN (s) : STRLEN (s))
+
+#define MBLEN(s, n)    ((MB_CUR_MAX > 1) ? mblen ((s), (n)) : 1)
+#define MBRLEN(s, n, p)        ((MB_CUR_MAX > 1) ? mbrlen ((s), (n), (p)) : 1)
+
+#else /* !HANDLE_MULTIBYTE */
+
+#undef MB_LEN_MAX
+#undef MB_CUR_MAX
+
+#define MB_LEN_MAX     1
+#define MB_CUR_MAX     1
+
+#undef xstrchr
+#define xstrchr(s, c)  strchr(s, c)
+
+#ifndef MB_INVALIDCH
+#define MB_INVALIDCH(x)                (0)
+#define MB_NULLWCH(x)          (0)
+#endif
+
+#define MB_STRLEN(s)           (STRLEN(s))
+
+#define MBLEN(s, n)            1
+#define MBRLEN(s, n, p)                1
+
+#ifndef wchar_t
+#  define wchar_t      int
+#endif
+
+#endif /* !HANDLE_MULTIBYTE */
+
+/* Declare and initialize a multibyte state.  Call must be terminated
+   with `;'. */
+#if defined (HANDLE_MULTIBYTE)
+#  define DECLARE_MBSTATE \
+       mbstate_t state; \
+       memset (&state, '\0', sizeof (mbstate_t))
+#else
+#  define DECLARE_MBSTATE
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Initialize or reinitialize a multibyte state named `state'.  Call must be
+   terminated with `;'. */
+#if defined (HANDLE_MULTIBYTE)
+#  define INITIALIZE_MBSTATE memset (&state, '\0', sizeof (mbstate_t))
+#else
+#  define INITIALIZE_MBSTATE
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Advance one (possibly multi-byte) character in string _STR of length
+   _STRSIZE, starting at index _I.  STATE must have already been declared. */
+#if defined (HANDLE_MULTIBYTE)
+#  define ADVANCE_CHAR(_str, _strsize, _i) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _f; \
+\
+           _f = is_basic ((_str)[_i]); \
+           if (_f) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_str) + (_i), (_strsize) - (_i), &state); \
+             } \
+\
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               (_i)++; \
+             } \
+           else if (mblength == 0) \
+             (_i)++; \
+           else \
+             (_i) += mblength; \
+         } \
+       else \
+         (_i)++; \
+      } \
+    while (0)
+#else
+#  define ADVANCE_CHAR(_str, _strsize, _i)     (_i)++
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Advance one (possibly multibyte) character in the string _STR of length
+   _STRSIZE.
+   SPECIAL:  assume that _STR will be incremented by 1 after this call. */
+#if defined (HANDLE_MULTIBYTE)
+#  define ADVANCE_CHAR_P(_str, _strsize) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _f; \
+\
+           _f = is_basic (*(_str)); \
+           if (_f) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_str), (_strsize), &state); \
+             } \
+\
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             (_str) += (mblength < 1) ? 0 : (mblength - 1); \
+         } \
+      } \
+    while (0)
+#else
+#  define ADVANCE_CHAR_P(_str, _strsize)
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Back up one (possibly multi-byte) character in string _STR of length
+   _STRSIZE, starting at index _I.  STATE must have already been declared. */
+#if defined (HANDLE_MULTIBYTE)
+#  define BACKUP_CHAR(_str, _strsize, _i) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _x, _p; /* _x == temp index into string, _p == prev index */ \
+\
+           _x = _p = 0; \
+           while (_x < (_i)) \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_str) + (_x), (_strsize) - (_x), &state); \
+\
+               if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+                 { \
+                   state = state_bak; \
+                   _x++; \
+                 } \
+               else if (mblength == 0) \
+                 _x++; \
+               else \
+                 { \
+                   _p = _x; /* _p == start of prev mbchar */ \
+                   _x += mblength; \
+                 } \
+             } \
+           (_i) = _p; \
+         } \
+       else \
+         (_i)--; \
+      } \
+    while (0)
+#else
+#  define BACKUP_CHAR(_str, _strsize, _i)      (_i)--
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Back up one (possibly multibyte) character in the string _BASE of length
+   _STRSIZE starting at _STR (_BASE <= _STR <= (_BASE + _STRSIZE) ).
+   SPECIAL: DO NOT assume that _STR will be decremented by 1 after this call. */
+#if defined (HANDLE_MULTIBYTE)
+#  define BACKUP_CHAR_P(_base, _strsize, _str) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           char *_x, _p; /* _x == temp pointer into string, _p == prev pointer */ \
+\
+           _x = _p = _base; \
+           while (_x < (_str)) \
+             { \
+               state_bak = state; \
+               mblength = mbrlen (_x, (_strsize) - _x, &state); \
+\
+               if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+                 { \
+                   state = state_bak; \
+                   _x++; \
+                 } \
+               else if (mblength == 0) \
+                 _x++; \
+               else \
+                 { \
+                   _p = _x; /* _p == start of prev mbchar */ \
+                   _x += mblength; \
+                 } \
+             } \
+           (_str) = _p; \
+         } \
+       else \
+         (_str)--; \
+      } \
+    while (0)
+#else
+#  define BACKUP_CHAR_P(_base, _strsize, _str) (_str)--
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Copy a single character from the string _SRC to the string _DST.
+   _SRCEND is a pointer to the end of _SRC. */
+#if defined (HANDLE_MULTIBYTE)
+#  define COPY_CHAR_P(_dst, _src, _srcend) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _k; \
+\
+           _k = is_basic (*(_src)); \
+           if (_k) \
+             mblength = 1; \
+           else \
+             { \
+               state_bak = state; \
+               mblength = mbrlen ((_src), (_srcend) - (_src), &state); \
+             } \
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             mblength = (mblength < 1) ? 1 : mblength; \
+\
+           for (_k = 0; _k < mblength; _k++) \
+             *(_dst)++ = *(_src)++; \
+         } \
+       else \
+         *(_dst)++ = *(_src)++; \
+      } \
+    while (0)
+#else
+#  define COPY_CHAR_P(_dst, _src, _srcend)     *(_dst)++ = *(_src)++
+#endif  /* !HANDLE_MULTIBYTE */
+
+/* Copy a single character from the string _SRC at index _SI to the string
+   _DST at index _DI.  _SRCEND is a pointer to the end of _SRC. */
+#if defined (HANDLE_MULTIBYTE)
+#  define COPY_CHAR_I(_dst, _di, _src, _srcend, _si) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _k; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src)+(_si)), &state); \
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             mblength = (mblength < 1) ? 1 : mblength; \
+\
+           for (_k = 0; _k < mblength; _k++) \
+             _dst[_di++] = _src[_si++]; \
+         } \
+       else \
+         _dst[_di++] = _src[_si++]; \
+      } \
+    while (0)
+#else
+#  define COPY_CHAR_I(_dst, _di, _src, _srcend, _si)   _dst[_di++] = _src[_si++]
+#endif  /* !HANDLE_MULTIBYTE */
+
+/****************************************************************
+ *                                                             *
+ * The following are only guaranteed to work in subst.c                *
+ *                                                             *
+ ****************************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+#  define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+           int _i; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_slen) - (_si), &state); \
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             mblength = (mblength < 1) ? 1 : mblength; \
+\
+           temp = xmalloc (mblength + 2); \
+           temp[0] = _escchar; \
+           for (_i = 0; _i < mblength; _i++) \
+             temp[_i + 1] = _src[_si++]; \
+           temp[mblength + 1] = '\0'; \
+\
+           goto add_string; \
+         } \
+       else \
+         { \
+           _dst[0] = _escchar; \
+           _dst[1] = _sc; \
+         } \
+      } \
+    while (0)
+#else
+#  define SCOPY_CHAR_I(_dst, _escchar, _sc, _src, _si, _slen) \
+    _dst[0] = _escchar; \
+    _dst[1] = _sc
+#endif  /* !HANDLE_MULTIBYTE */
+
+#if defined (HANDLE_MULTIBYTE)
+#  define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           mbstate_t state_bak; \
+           size_t mblength; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_srcend) - ((_src) + (_si)), &state); \
+           if (mblength == (size_t)-2 || mblength == (size_t)-1) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           else \
+             mblength = (mblength < 1) ? 1 : mblength; \
+\
+           FASTCOPY(((_src) + (_si)), (_dst), mblength); \
+\
+           (_dst) += mblength; \
+           (_si) += mblength; \
+         } \
+       else \
+         { \
+           *(_dst)++ = _src[(_si)]; \
+           (_si)++; \
+         } \
+      } \
+    while (0)
+#else
+#  define SCOPY_CHAR_M(_dst, _src, _srcend, _si) \
+       *(_dst)++ = _src[(_si)]; \
+       (_si)++
+#endif  /* !HANDLE_MULTIBYTE */
+
+#if HANDLE_MULTIBYTE
+#  define SADD_MBCHAR(_dst, _src, _si, _srcsize) \
+    do \
+      { \
+       if (MB_CUR_MAX > 1) \
+         { \
+           int i; \
+           mbstate_t state_bak; \
+           size_t mblength; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \
+           if (mblength == (size_t)-1 || mblength == (size_t)-2) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           if (mblength < 1) \
+             mblength = 1; \
+\
+           _dst = (char *)xmalloc (mblength + 1); \
+           for (i = 0; i < mblength; i++) \
+             (_dst)[i] = (_src)[(_si)++]; \
+           (_dst)[mblength] = '\0'; \
+\
+           goto add_string; \
+         } \
+      } \
+    while (0)
+
+#else
+#  define SADD_MBCHAR(_dst, _src, _si, _srcsize)
+#endif
+
+/* Watch out when using this -- it's just straight textual subsitution */
+#if defined (HANDLE_MULTIBYTE)
+#  define SADD_MBQCHAR_BODY(_dst, _src, _si, _srcsize) \
+\
+           int i; \
+           mbstate_t state_bak; \
+           size_t mblength; \
+\
+           state_bak = state; \
+           mblength = mbrlen ((_src) + (_si), (_srcsize) - (_si), &state); \
+           if (mblength == (size_t)-1 || mblength == (size_t)-2) \
+             { \
+               state = state_bak; \
+               mblength = 1; \
+             } \
+           if (mblength < 1) \
+             mblength = 1; \
+\
+           (_dst) = (char *)xmalloc (mblength + 2); \
+           (_dst)[0] = CTLESC; \
+           for (i = 0; i < mblength; i++) \
+             (_dst)[i+1] = (_src)[(_si)++]; \
+           (_dst)[mblength+1] = '\0'; \
+\
+           goto add_string
+
+#endif /* HANDLE_MULTIBYTE */
+#endif /* _SH_MBUTIL_H_ */
index 11d86b080cfaf0baa921b45909e256aae3e28f9a..bd2c3d59be070be0400222eb5878e172c893241a 100644 (file)
@@ -365,6 +365,16 @@ xstrmatch (pattern, string, flags)
   int ret;
   size_t n;
   wchar_t *wpattern, *wstring;
+  size_t plen, slen, mplen, mslen;
+
+#if 0
+  plen = strlen (pattern);
+  mplen = mbstrlen (pattern);
+  if (plen == mplen && strlen (string) == mbstrlen (string))
+#else
+  if (mbsmbchar (string) == 0 && mbsmbchar (pattern) == 0)
+#endif
+    return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags));
 
   if (MB_CUR_MAX == 1)
     return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags));
diff --git a/lib/glob/smatch.c~ b/lib/glob/smatch.c~
new file mode 100644 (file)
index 0000000..0e48024
--- /dev/null
@@ -0,0 +1,398 @@
+/* strmatch.c -- ksh-like extended pattern matching for the shell and filename
+               globbing. */
+
+/* Copyright (C) 1991-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 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <config.h>
+
+#include <stdio.h>     /* for debugging */
+                               
+#include "strmatch.h"
+#include <chartypes.h>
+
+#include "bashansi.h"
+#include "shmbutil.h"
+#include "xmalloc.h"
+
+/* First, compile `sm_loop.c' for single-byte characters. */
+#define CHAR   unsigned char
+#define U_CHAR unsigned char
+#define XCHAR  char
+#define INT    int
+#define L(CS)  CS
+#define INVALID        -1
+
+#undef STREQ
+#undef STREQN
+#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0)
+#define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, n) == 0)
+
+/* We use strcoll(3) for range comparisons in bracket expressions,
+   even though it can have unwanted side effects in locales
+   other than POSIX or US.  For instance, in the de locale, [A-Z] matches
+   all characters. */
+
+#if defined (HAVE_STRCOLL)
+/* Helper function for collating symbol equivalence. */
+static int rangecmp (c1, c2)
+     int c1, c2;
+{
+  static char s1[2] = { ' ', '\0' };
+  static char s2[2] = { ' ', '\0' };
+  int ret;
+
+  /* Eight bits only.  Period. */
+  c1 &= 0xFF;
+  c2 &= 0xFF;
+
+  if (c1 == c2)
+    return (0);
+
+  s1[0] = c1;
+  s2[0] = c2;
+
+  if ((ret = strcoll (s1, s2)) != 0)
+    return ret;
+  return (c1 - c2);
+}
+#else /* !HAVE_STRCOLL */
+#  define rangecmp(c1, c2)     ((int)(c1) - (int)(c2))
+#endif /* !HAVE_STRCOLL */
+
+#if defined (HAVE_STRCOLL)
+static int
+collequiv (c1, c2)
+     int c1, c2;
+{
+  return (rangecmp (c1, c2) == 0);
+}
+#else
+#  define collequiv(c1, c2)    ((c1) == (c2))
+#endif
+
+#define _COLLSYM       _collsym
+#define __COLLSYM      __collsym
+#define POSIXCOLL      posix_collsyms
+#include "collsyms.h"
+
+static int
+collsym (s, len)
+     CHAR *s;
+     int len;
+{
+  register struct _collsym *csp;
+  char *x;
+
+  x = (char *)s;
+  for (csp = posix_collsyms; csp->name; csp++)
+    {
+      if (STREQN(csp->name, x, len) && csp->name[len] == '\0')
+       return (csp->code);
+    }
+  if (len == 1)
+    return s[0];
+  return INVALID;
+}
+
+/* unibyte character classification */
+#if !defined (isascii) && !defined (HAVE_ISASCII)
+#  define isascii(c)   ((unsigned int)(c) <= 0177)
+#endif
+
+enum char_class
+  {
+    CC_NO_CLASS = 0,
+    CC_ASCII, CC_ALNUM, CC_ALPHA, CC_BLANK, CC_CNTRL, CC_DIGIT, CC_GRAPH,
+    CC_LOWER, CC_PRINT, CC_PUNCT, CC_SPACE, CC_UPPER, CC_WORD, CC_XDIGIT
+  };
+
+static char const *const cclass_name[] =
+  {
+    "",
+    "ascii", "alnum", "alpha", "blank", "cntrl", "digit", "graph",
+    "lower", "print", "punct", "space", "upper", "word", "xdigit"
+  };
+
+#define N_CHAR_CLASS (sizeof(cclass_name) / sizeof (cclass_name[0]))
+
+static int
+is_cclass (c, name)
+     int c;
+     const char *name;
+{
+  enum char_class char_class = CC_NO_CLASS;
+  int i, result;
+
+  for (i = 1; i < N_CHAR_CLASS; i++)
+    {
+      if (STREQ (name, cclass_name[i]))
+       {
+         char_class = (enum char_class)i;
+         break;
+       }
+    }
+
+  if (char_class == 0)
+    return -1;
+
+  switch (char_class)
+    {
+      case CC_ASCII:
+       result = isascii (c);
+       break;
+      case CC_ALNUM:
+       result = ISALNUM (c);
+       break;
+      case CC_ALPHA:
+       result = ISALPHA (c);
+       break;
+      case CC_BLANK:  
+       result = ISBLANK (c);
+       break;
+      case CC_CNTRL:
+       result = ISCNTRL (c);
+       break;
+      case CC_DIGIT:
+       result = ISDIGIT (c);
+       break;
+      case CC_GRAPH:
+       result = ISGRAPH (c);
+       break;
+      case CC_LOWER:
+       result = ISLOWER (c);
+       break;
+      case CC_PRINT: 
+       result = ISPRINT (c);
+       break;
+      case CC_PUNCT:
+       result = ISPUNCT (c);
+       break;
+      case CC_SPACE:
+       result = ISSPACE (c);
+       break;
+      case CC_UPPER:
+       result = ISUPPER (c);
+       break;
+      case CC_WORD:
+        result = (ISALNUM (c) || c == '_');
+       break;
+      case CC_XDIGIT:
+       result = ISXDIGIT (c);
+       break;
+      default:
+       result = -1;
+       break;
+    }
+
+  return result;  
+}
+
+/* Now include `sm_loop.c' for single-byte characters. */
+/* The result of FOLD is an `unsigned char' */
+# define FOLD(c) ((flags & FNM_CASEFOLD) \
+       ? TOLOWER ((unsigned char)c) \
+       : ((unsigned char)c))
+
+#define FCT                    internal_strmatch
+#define GMATCH                 gmatch
+#define COLLSYM                        collsym
+#define PARSE_COLLSYM          parse_collsym
+#define BRACKMATCH             brackmatch
+#define PATSCAN                        patscan
+#define STRCOMPARE             strcompare
+#define EXTMATCH               extmatch
+#define STRCHR(S, C)           strchr((S), (C))
+#define STRCOLL(S1, S2)                strcoll((S1), (S2))
+#define STRLEN(S)              strlen(S)
+#define STRCMP(S1, S2)         strcmp((S1), (S2))
+#define RANGECMP(C1, C2)       rangecmp((C1), (C2))
+#define COLLEQUIV(C1, C2)      collequiv((C1), (C2))
+#define CTYPE_T                        enum char_class
+#define IS_CCLASS(C, S)                is_cclass((C), (S))
+#include "sm_loop.c"
+
+#if HANDLE_MULTIBYTE
+
+#  define CHAR         wchar_t
+#  define U_CHAR       wint_t
+#  define XCHAR                wchar_t
+#  define INT          wint_t
+#  define L(CS)                L##CS
+#  define INVALID      WEOF
+
+#  undef STREQ
+#  undef STREQN
+#  define STREQ(s1, s2) ((wcscmp (s1, s2) == 0))
+#  define STREQN(a, b, n) ((a)[0] == (b)[0] && wcsncmp(a, b, n) == 0)
+
+static int
+rangecmp_wc (c1, c2)
+     wint_t c1, c2;
+{
+  static wchar_t s1[2] = { L' ', L'\0' };
+  static wchar_t s2[2] = { L' ', L'\0' };
+
+  if (c1 == c2)
+    return 0;
+
+  s1[0] = c1;
+  s2[0] = c2;
+
+  return (wcscoll (s1, s2));
+}
+
+static int
+collequiv_wc (c, equiv)
+     wint_t c, equiv;
+{
+  return (!(c - equiv));
+}
+
+/* Helper function for collating symbol. */
+#  define _COLLSYM     _collwcsym
+#  define __COLLSYM    __collwcsym
+#  define POSIXCOLL    posix_collwcsyms
+#  include "collsyms.h"
+
+static wint_t
+collwcsym (s, len)
+     wchar_t *s;
+     int len;
+{
+  register struct _collwcsym *csp;
+
+  for (csp = posix_collwcsyms; csp->name; csp++)
+    {
+      if (STREQN(csp->name, s, len) && csp->name[len] == L'\0')
+       return (csp->code);
+    }
+  if (len == 1)
+    return s[0];
+  return INVALID;
+}
+
+static int
+is_wcclass (wc, name)
+     wint_t wc;
+     wchar_t *name;
+{
+  char *mbs;
+  mbstate_t state;
+  size_t mbslength;
+  wctype_t desc;
+  int want_word;
+
+  if ((wctype ("ascii") == (wctype_t)0) && (wcscmp (name, L"ascii") == 0))
+    {
+      int c;
+
+      if ((c = wctob (wc)) == EOF)
+       return 0;
+      else
+        return (c <= 0x7F);
+    }
+
+  want_word = (wcscmp (name, L"word") == 0);
+  if (want_word)
+    name = L"alnum";
+
+  memset (&state, '\0', sizeof (mbstate_t));
+  mbs = (char *) malloc (wcslen(name) * MB_CUR_MAX + 1);
+  mbslength = wcsrtombs(mbs, (const wchar_t **)&name, (wcslen(name) * MB_CUR_MAX + 1), &state);
+
+  if (mbslength == (size_t)-1 || mbslength == (size_t)-2)
+    {
+      free (mbs);
+      return -1;
+    }
+  desc = wctype (mbs);
+  free (mbs);
+
+  if (desc == (wctype_t)0)
+    return -1;
+
+  if (want_word)
+    return (iswctype (wc, desc) || wc == L'_');
+  else
+    return (iswctype (wc, desc));
+}
+
+/* Now include `sm_loop.c' for multibyte characters. */
+#define FOLD(c) ((flags & FNM_CASEFOLD) && iswupper (c) ? towlower (c) : (c))
+#define FCT                    internal_wstrmatch
+#define GMATCH                 gmatch_wc
+#define COLLSYM                        collwcsym
+#define PARSE_COLLSYM          parse_collwcsym
+#define BRACKMATCH             brackmatch_wc
+#define PATSCAN                        patscan_wc
+#define STRCOMPARE             wscompare
+#define EXTMATCH               extmatch_wc
+#define STRCHR(S, C)           wcschr((S), (C))
+#define STRCOLL(S1, S2)                wcscoll((S1), (S2))
+#define STRLEN(S)              wcslen(S)
+#define STRCMP(S1, S2)         wcscmp((S1), (S2))
+#define RANGECMP(C1, C2)       rangecmp_wc((C1), (C2))
+#define COLLEQUIV(C1, C2)      collequiv_wc((C1), (C2))
+#define CTYPE_T                        enum char_class
+#define IS_CCLASS(C, S)                is_wcclass((C), (S))
+#include "sm_loop.c"
+
+#endif /* HAVE_MULTIBYTE */
+
+int
+xstrmatch (pattern, string, flags)
+     char *pattern;
+     char *string;
+     int flags;
+{
+#if HANDLE_MULTIBYTE
+  int ret;
+  size_t n;
+  wchar_t *wpattern, *wstring;
+  size_t plen, slen, mplen, mslen;
+
+  plen = strlen (pattern);
+  mplen = mbstrlen (pattern);
+  if (plen == mplen && strlen (string) == mbstrlen (string))
+    return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags));
+
+  if (MB_CUR_MAX == 1)
+    return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags));
+
+  n = xdupmbstowcs (&wpattern, NULL, pattern);
+  if (n == (size_t)-1 || n == (size_t)-2)
+    return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags));
+
+  n = xdupmbstowcs (&wstring, NULL, string);
+  if (n == (size_t)-1 || n == (size_t)-2)
+    {
+      free (wpattern);
+      return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags));
+    }
+
+  ret = internal_wstrmatch (wpattern, wstring, flags);
+
+  free (wpattern);
+  free (wstring);
+
+  return ret;
+#else
+  return (internal_strmatch ((unsigned char *)pattern, (unsigned char *)string, flags));
+#endif /* !HANDLE_MULTIBYTE */
+}
index 90baa176c5e7cbb4f9162bf0035fa4ea6290e68b..2a9d89d4874697a68a0ed68cb0836f7a296514ab 100644 (file)
@@ -763,10 +763,14 @@ as if the "!\fIn\fP" history expansion had been specified.
 .B
 yank\-last\-arg (M\-.\^, M\-_\^)
 Insert the last argument to the previous command (the last word of
-the previous history entry).  With an argument,
-behave exactly like \fByank\-nth\-arg\fP.
+the previous history entry).
+With a numeric argument, behave exactly like \fByank\-nth\-arg\fP.
 Successive calls to \fByank\-last\-arg\fP move back through the history
-list, inserting the last argument of each line in turn.
+list, inserting the last word (or the word specified by the argument to
+the first call) of each line in turn.
+Any numeric argument supplied to these successive calls determines
+the direction to move through the history.  A negative argument switches
+the direction through the history (back or forward).
 The history expansion facilities are used to extract the last argument,
 as if the "!$" history expansion had been specified.
 .PD
index 60a9caf8081d2cef7fe39e506ebdb0acaba80668..90baa176c5e7cbb4f9162bf0035fa4ea6290e68b 100644 (file)
@@ -492,6 +492,11 @@ completion.
 If set to \fBOff\fP, the leading `.' must be
 supplied by the user in the filename to be completed.
 .TP
+.B menu\-complete\-display\-prefix (Off)
+If set to \fBOn\fP, menu completion displays the common prefix of the
+list of possible completions (which may be empty) before cycling through
+the list.
+.TP
 .B output\-meta (Off)
 If set to \fBOn\fP, readline will display characters with the
 eighth bit set directly rather than as a meta-prefixed escape
index 985210ea7d71af0905aa1b6d3890149215a1858c..d00f729b54a31eaf42728c59b2342fe458da4dad 100644 (file)
@@ -1120,10 +1120,14 @@ as if the @samp{!@var{n}} history expansion had been specified.
 
 @item yank-last-arg (M-. or M-_)
 Insert last argument to the previous command (the last word of the
-previous history entry).  With an
-argument, behave exactly like @code{yank-nth-arg}.
+previous history entry).
+With a numeric argument, behave exactly like @code{yank-nth-arg}.
 Successive calls to @code{yank-last-arg} move back through the history
-list, inserting the last argument of each line in turn.
+list, inserting the last word (or the word specified by the argument to
+the first call) of each line in turn.
+Any numeric argument supplied to these successive calls determines
+the direction to move through the history.  A negative argument switches
+the direction through the history (back or forward).
 The history expansion facilities are used to extract the last argument,
 as if the @samp{!$} history expansion had been specified.
 
index f34aad515aa4677c34acd292dae4d7a6447f4ec7..d00f729b54a31eaf42728c59b2342fe458da4dad 100644 (file)
@@ -591,6 +591,12 @@ If set to @samp{off}, the leading @samp{.} must be
 supplied by the user in the filename to be completed.
 This variable is @samp{on} by default.
 
+@item menu-complete-display-prefix
+@vindex menu-complete-display-prefix
+If set to @samp{on}, menu completion displays the common prefix of the
+list of possible completions (which may be empty) before cycling through
+the list.  The default is @samp{off}.
+
 @item output-meta
 @vindex output-meta
 If set to @samp{on}, Readline will display characters with the
@@ -1114,10 +1120,14 @@ as if the @samp{!@var{n}} history expansion had been specified.
 
 @item yank-last-arg (M-. or M-_)
 Insert last argument to the previous command (the last word of the
-previous history entry).  With an
-argument, behave exactly like @code{yank-nth-arg}.
+previous history entry).
+With a numeric argument, behave exactly like @code{yank-nth-arg}.
 Successive calls to @code{yank-last-arg} move back through the history
-list, inserting the last argument of each line in turn.
+list, inserting the last word (or the word specified by the argument to
+the first call) of each line in turn.
+Any numeric argument supplied to these successive calls determines
+the direction to move through the history.  A negative argument switches
+the direction through the history (back or forward).
 The history expansion facilities are used to extract the last argument,
 as if the @samp{!$} history expansion had been specified.
 
@@ -1229,7 +1239,7 @@ Kill from point to the end of the current word, or if between
 words, to the end of the next word.
 Word boundaries are the same as @code{shell-forward-word}.
 
-@item backward-kill-word ()
+@item shell-backward-kill-word ()
 Kill the word behind point.
 Word boundaries are the same as @code{shell-backward-word}.
 @end ifset
index 60f304102b20ca1ae4b68af79c862131f1b74a79..1a78783f9aac29bc5c60b92d2ef6d217bd5ba0c1 100644 (file)
@@ -640,7 +640,7 @@ rl_yank_last_arg (count, key)
     {
       if (undo_needed)
        rl_do_undo ();
-      if (count < 1)
+      if (count < 0)           /* XXX - was < 1 */
         direction = -direction;
       history_skip += direction;
       if (history_skip < 0)
index a4d6d14824ad46f01ad2726f74a43e6ec25c89fa..60f304102b20ca1ae4b68af79c862131f1b74a79 100644 (file)
@@ -147,7 +147,7 @@ _rl_copy_to_kill_ring (text, append)
          strcat (new, old);
        }
       xfree (old);
-      free (text);
+      xfree (text);
       rl_kill_ring[slot] = new;
     }
   else
@@ -601,7 +601,7 @@ rl_yank_nth_arg_internal (count, ignore, history_skip)
 #endif /* VI_MODE */
 
   rl_insert_text (arg);
-  free (arg);
+  xfree (arg);
 
   rl_end_undo_group ();
   return 0;
index 6f68a132c8644c32509c570088acdd895c761da4..d97cd99b61fbc36e310d2ddf7ed7779654958ec8 100644 (file)
@@ -92,7 +92,7 @@ CSOURCES = clktck.c clock.c getcwd.c getenv.c oslib.c setlinebuf.c \
           mktime.c strftime.c mbschr.c zcatfd.c zmapfd.c winsize.c eaccess.c \
           wcsdup.c fpurge.c zgetline.c mbscmp.c uconvert.c ufuncs.c \
           casemod.c dprintf.c input_avail.c mbscasecmp.c fnxform.c \
-          strchrnul.c unicode.c wcswidth.c
+          strchrnul.c unicode.c wcswidth.c shmbchar.c
 
 # The header files for this library.
 HSOURCES = 
@@ -106,7 +106,7 @@ OBJECTS = clktck.o clock.o getenv.o oslib.o setlinebuf.o strnlen.o \
          strtrans.o snprintf.o mailstat.o fmtulong.o \
          fmtullong.o fmtumax.o zcatfd.o zmapfd.o winsize.o wcsdup.o \
          fpurge.o zgetline.o mbscmp.o uconvert.o ufuncs.o casemod.o \
-         input_avail.o mbscasecmp.o fnxform.o unicode.o ${LIBOBJS}
+         input_avail.o mbscasecmp.o fnxform.o unicode.o shmbchar.o ${LIBOBJS}
 
 SUPPORT = Makefile
 
@@ -169,6 +169,7 @@ pathcanon.o: pathcanon.c
 pathphys.o: pathphys.c
 rename.o: rename.c
 setlinebuf.o: setlinebuf.c
+shmbchar.o: shmbchar.c
 shquote.o: shquote.c
 shtty.o: shtty.c
 snprintf.o: snprintf.c
@@ -240,6 +241,7 @@ pathcanon.o: ${BUILD_DIR}/config.h
 pathphys.o: ${BUILD_DIR}/config.h
 rename.o: ${BUILD_DIR}/config.h
 setlinebuf.o: ${BUILD_DIR}/config.h
+shmbchare.o: ${BUILD_DIR}/config.h
 shquote.o: ${BUILD_DIR}/config.h
 shtty.o: ${BUILD_DIR}/config.h
 snprintf.o: ${BUILD_DIR}/config.h
@@ -513,7 +515,7 @@ wcswidth.o: ${BASHINCDIR}/stdc.h
 
 mbschr.o: ${topdir}/bashansi.h
 mbschr.o: ${BASHINCDIR}/ansi_stdlib.h
-mbschr.o: ${BASHINCDIR}/shmbutil.h
+mbschr.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
 
 zgetline.o: ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 zgetline.o: ${BASHINCDIR}/stdc.h
@@ -532,7 +534,7 @@ casemod.o: ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 casemod.o: ${BASHINCDIR}/stdc.h
 casemod.o: ${topdir}/xmalloc.h
 casemod.o: ${topdir}/bashtypes.h
-casemod.o: ${BASHINCDIR}/shmbutil.h
+casemod.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmbchar.h
 casemod.o: ${topdir}/bashintl.h ${LIBINTL_H} ${BASHINCDIR}/gettext.h
 
 dprintf.o: ${BASHINCDIR}/stdc.h
@@ -548,3 +550,5 @@ fnxform.o: ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 fnxform.o: ${BASHINCDIR}/stdc.h
 fnxform.o: ${topdir}/bashtypes.h
 fnxform.o: ${topdir}/bashintl.h ${LIBINTL_H} ${BASHINCDIR}/gettext.h
+
+shmbchar.o: ${BASHINCDIR}/shmbchar.h
index c99e98281e47349e99214837d59d882799f7b19a..0401b2f5fc46527eea32908aa64509ace7c11981 100644 (file)
@@ -92,7 +92,7 @@ CSOURCES = clktck.c clock.c getcwd.c getenv.c oslib.c setlinebuf.c \
           mktime.c strftime.c mbschr.c zcatfd.c zmapfd.c winsize.c eaccess.c \
           wcsdup.c fpurge.c zgetline.c mbscmp.c uconvert.c ufuncs.c \
           casemod.c dprintf.c input_avail.c mbscasecmp.c fnxform.c \
-          strchrnul.c unicode.c
+          strchrnul.c unicode.c wcswidth.c shmbchar.c
 
 # The header files for this library.
 HSOURCES = 
@@ -106,7 +106,7 @@ OBJECTS = clktck.o clock.o getenv.o oslib.o setlinebuf.o strnlen.o \
          strtrans.o snprintf.o mailstat.o fmtulong.o \
          fmtullong.o fmtumax.o zcatfd.o zmapfd.o winsize.o wcsdup.o \
          fpurge.o zgetline.o mbscmp.o uconvert.o ufuncs.o casemod.o \
-         input_avail.o mbscasecmp.o fnxform.o unicode.o ${LIBOBJS}
+         input_avail.o mbscasecmp.o fnxform.o unicode.o shmbchar.o ${LIBOBJS}
 
 SUPPORT = Makefile
 
@@ -169,6 +169,7 @@ pathcanon.o: pathcanon.c
 pathphys.o: pathphys.c
 rename.o: rename.c
 setlinebuf.o: setlinebuf.c
+shmbchar.o: shmbchar.c
 shquote.o: shquote.c
 shtty.o: shtty.c
 snprintf.o: snprintf.c
@@ -196,6 +197,7 @@ uconvert.o: uconvert.c
 ufuncs.o: ufuncs.c
 vprint.o: vprint.c
 wcsdup.o: wcsdup.c
+wcswidth.o: wcswidth.c
 mbschr.o: mbschr.c
 zcatfd.o: zcatfd.c
 zmapfd.o: zmapfd.c
@@ -239,6 +241,7 @@ pathcanon.o: ${BUILD_DIR}/config.h
 pathphys.o: ${BUILD_DIR}/config.h
 rename.o: ${BUILD_DIR}/config.h
 setlinebuf.o: ${BUILD_DIR}/config.h
+shmbchare.o: ${BUILD_DIR}/config.h
 shquote.o: ${BUILD_DIR}/config.h
 shtty.o: ${BUILD_DIR}/config.h
 snprintf.o: ${BUILD_DIR}/config.h
@@ -266,6 +269,7 @@ uconvert.o: ${BUILD_DIR}/config.h
 ufuncs.o: ${BUILD_DIR}/config.h
 vprint.o: ${BUILD_DIR}/config.h
 wcsdup.o: ${BUILD_DIR}/config.h
+wcswidth.o: ${BUILD_DIR}/config.h
 mbschr.o: ${BUILD_DIR}/config.h
 zcatfd.o: ${BUILD_DIR}/config.h
 zgetline.o: ${BUILD_DIR}/config.h
@@ -506,9 +510,12 @@ wcsdup.o: ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 wcsdup.o: ${BASHINCDIR}/stdc.h
 wcsdup.o: ${topdir}/xmalloc.h
 
+wcswidth.o: ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
+wcswidth.o: ${BASHINCDIR}/stdc.h
+
 mbschr.o: ${topdir}/bashansi.h
 mbschr.o: ${BASHINCDIR}/ansi_stdlib.h
-mbschr.o: ${BASHINCDIR}/shmbutil.h
+mbschr.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmchar.h
 
 zgetline.o: ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 zgetline.o: ${BASHINCDIR}/stdc.h
@@ -527,7 +534,7 @@ casemod.o: ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 casemod.o: ${BASHINCDIR}/stdc.h
 casemod.o: ${topdir}/xmalloc.h
 casemod.o: ${topdir}/bashtypes.h
-casemod.o: ${BASHINCDIR}/shmbutil.h
+casemod.o: ${BASHINCDIR}/shmbutil.h ${BASHINCDIR}/shmchar.h
 casemod.o: ${topdir}/bashintl.h ${LIBINTL_H} ${BASHINCDIR}/gettext.h
 
 dprintf.o: ${BASHINCDIR}/stdc.h
@@ -543,3 +550,5 @@ fnxform.o: ${topdir}/bashansi.h ${BASHINCDIR}/ansi_stdlib.h
 fnxform.o: ${BASHINCDIR}/stdc.h
 fnxform.o: ${topdir}/bashtypes.h
 fnxform.o: ${topdir}/bashintl.h ${LIBINTL_H} ${BASHINCDIR}/gettext.h
+
+shmbchar.o: ${BASHINCDIR}/shmbchar.h
index bb91a1532016e3939820184db7ec9edb4298ad5e..d7e1b5ada5c9e809b128c2b7e60340b9f05b0cc9 100644 (file)
@@ -72,7 +72,7 @@ curencoding ()
   mod = strchr (dot, '@');
   if (mod)
     *mod = '\0';
-  return dot;
+  return ++dot;
 #endif
 }  
 
diff --git a/lib/sh/shmbchar.c b/lib/sh/shmbchar.c
new file mode 100644 (file)
index 0000000..c5badc1
--- /dev/null
@@ -0,0 +1,92 @@
+/* Copyright (C) 2001, 2006, 2009, 2010 Free Software Foundation, Inc.
+
+   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 3 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, see <http://www.gnu.org/licenses/>.  */
+
+
+#include <config.h>
+
+#if defined (HANDLE_MULTIBYTE)
+#include <stdlib.h>
+#include <limits.h>
+
+#include <shmbutil.h>
+#include <shmbchar.h>
+
+#if IS_BASIC_ASCII
+
+/* Bit table of characters in the ISO C "basic character set".  */
+const unsigned int is_basic_table [UCHAR_MAX / 32 + 1] =
+{
+  0x00001a00,           /* '\t' '\v' '\f' */
+  0xffffffef,           /* ' '...'#' '%'...'?' */
+  0xfffffffe,           /* 'A'...'Z' '[' '\\' ']' '^' '_' */
+  0x7ffffffe            /* 'a'...'z' '{' '|' '}' '~' */
+  /* The remaining bits are 0.  */
+};
+
+#endif /* IS_BASIC_ASCII */
+
+size_t
+mbstrlen (s)
+     const char *s;
+{
+  size_t clen, nc;
+  mbstate_t mbs = { 0 }, mbsbak = { 0 };
+  int f;
+
+  nc = 0;
+  while (*s && (clen = (f = is_basic (*s)) ? 1 : mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
+    {
+      if (MB_INVALIDCH(clen))
+       {
+         clen = 1;     /* assume single byte */
+         mbs = mbsbak;
+       }
+
+      if (f == 0)
+       mbsbak = mbs;
+
+      s += clen;
+      nc++;
+    }
+  return nc;
+}
+
+/* Return pointer to first multibyte char in S, or NULL if none. */
+char *
+mbsmbchar (s)
+     const char *s;
+{
+  char *t;
+  size_t clen;
+  mbstate_t mbs = { 0 };
+
+  for (t = (char *)s; *t; t++)
+    {
+      if (is_basic (*t))
+       continue;
+
+      clen = mbrlen (t, MB_CUR_MAX, &mbs);
+
+      if (clen == 0)
+        return 0;
+      if (MB_INVALIDCH(clen))
+       continue;
+
+      if (clen > 1)
+       return t;
+    }
+  return 0;
+}
+#endif
diff --git a/lib/sh/shmbchar.c.save1 b/lib/sh/shmbchar.c.save1
new file mode 100644 (file)
index 0000000..df426e2
--- /dev/null
@@ -0,0 +1,64 @@
+/* Copyright (C) 2001, 2006, 2009, 2010 Free Software Foundation, Inc.
+
+   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 3 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, see <http://www.gnu.org/licenses/>.  */
+
+
+#include <config.h>
+
+#if defined (HANDLE_MULTIBYTE)
+#include <stdlib.h>
+#include <limits.h>
+
+#include <shmbutil.h>
+#include <shmbchar.h>
+
+#if IS_BASIC_ASCII
+
+/* Bit table of characters in the ISO C "basic character set".  */
+const unsigned int is_basic_table [UCHAR_MAX / 32 + 1] =
+{
+  0x00001a00,           /* '\t' '\v' '\f' */
+  0xffffffef,           /* ' '...'#' '%'...'?' */
+  0xfffffffe,           /* 'A'...'Z' '[' '\\' ']' '^' '_' */
+  0x7ffffffe            /* 'a'...'z' '{' '|' '}' '~' */
+  /* The remaining bits are 0.  */
+};
+
+#endif /* IS_BASIC_ASCII */
+
+size_t
+mbstrlen (s)
+     const char *s;
+{
+  size_t clen, nc;
+  mbstate_t mbs = { 0 }, mbsbak = { 0 };
+
+  nc = 0;
+  while (*s && (clen = is_basic (*s) ? 1 : mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
+    {
+      if (MB_INVALIDCH(clen))
+       {
+         clen = 1;     /* assume single byte */
+         mbs = mbsbak;
+       }
+
+      if (is_basic (*s) == 0)
+       mbsbak = mbs;
+
+      s += clen;
+      nc++;
+    }
+  return nc;
+}
+#endif
diff --git a/lib/sh/shmbchar.c~ b/lib/sh/shmbchar.c~
new file mode 100644 (file)
index 0000000..ea4e14e
--- /dev/null
@@ -0,0 +1,91 @@
+/* Copyright (C) 2001, 2006, 2009, 2010 Free Software Foundation, Inc.
+
+   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 3 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, see <http://www.gnu.org/licenses/>.  */
+
+
+#include <config.h>
+
+#if defined (HANDLE_MULTIBYTE)
+#include <stdlib.h>
+#include <limits.h>
+
+#include <shmbutil.h>
+#include <shmbchar.h>
+
+#if IS_BASIC_ASCII
+
+/* Bit table of characters in the ISO C "basic character set".  */
+const unsigned int is_basic_table [UCHAR_MAX / 32 + 1] =
+{
+  0x00001a00,           /* '\t' '\v' '\f' */
+  0xffffffef,           /* ' '...'#' '%'...'?' */
+  0xfffffffe,           /* 'A'...'Z' '[' '\\' ']' '^' '_' */
+  0x7ffffffe            /* 'a'...'z' '{' '|' '}' '~' */
+  /* The remaining bits are 0.  */
+};
+
+#endif /* IS_BASIC_ASCII */
+
+size_t
+mbstrlen (s)
+     const char *s;
+{
+  size_t clen, nc;
+  mbstate_t mbs = { 0 }, mbsbak = { 0 };
+
+  nc = 0;
+  while (*s && (clen = is_basic (*s) ? 1 : mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
+    {
+      if (MB_INVALIDCH(clen))
+       {
+         clen = 1;     /* assume single byte */
+         mbs = mbsbak;
+       }
+
+      if (is_basic (*s) == 0)
+       mbsbak = mbs;
+
+      s += clen;
+      nc++;
+    }
+  return nc;
+}
+
+/* Return pointer to first multibyte char in S, or NULL if none. */
+char *
+mbsmbchar (s)
+     const char *s;
+{
+  char *t;
+  size_t clen;
+  mbstate_t mbs = { 0 };
+
+  for (t = (char *)s; *t; t++)
+    {
+      if (is_basic (*t))
+       continue;
+
+      clen = mbrlen (t, MB_CUR_MAX, &mbs);
+
+      if (clen == 0)
+        return 0;
+      if (MB_INVALIDCH(clen))
+       continue;
+
+      if (clen > 1)
+       return t;
+    }
+  return 0;
+}
+#endif
index 4d717c218aab24ff5523041fb002a36380e43cff..41bd399e45ea9c93055f1be2b440c2659087fce9 100644 (file)
@@ -64,14 +64,19 @@ static iconv_t localconv;
 static char *
 stub_charset ()
 {
-  char *locale, *s;
+  char *locale, *s, *t;
 
   locale = get_locale_var ("LC_CTYPE");
-  if (locale == 0)
+  if (locale == 0 || *locale == 0)
     return "ASCII";
   s = strrchr (locale, '.');
   if (s)
-    return ++s;
+    {
+      t = strchr (s, '@');
+      if (t)
+       *t = 0;
+      return ++s;
+    }
   else if (STREQ (locale, "UTF-8"))
     return "UTF-8";
   else
index 2bc0ea343316fbba910c70c83bd6105c396829da..8d7c3cecf0138aa76a091aec57a3ec4ab9c23b77 100644 (file)
@@ -1,6 +1,6 @@
 /* unicode.c - functions to convert unicode characters */
 
-/* Copyright (C) 2006 Free Software Foundation, Inc.
+/* Copyright (C) 2010 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
@@ -56,7 +56,33 @@ extern char *get_locale_var __P((char *));
 
 static int u32init = 0;
 static int utf8locale = 0;
+#if defined (HAVE_ICONV)
 static iconv_t localconv;
+#endif
+
+#ifndef HAVE_LOCALE_CHARSET
+static char *
+stub_charset ()
+{
+  char *locale, *s;
+
+  locale = get_locale_var ("LC_CTYPE");
+  if (locale == 0 || *locale == 0)
+    return "ASCII";
+  s = strrchr (locale, '.');
+  if (s)
+    {
+      t = strchr (s, '@');
+      if (t)
+       *t = 0;
+      return ++s;
+    }
+  else if (STREQ (locale, "UTF-8"))
+    return "UTF-8";
+  else
+    return "ASCII";
+}
+#endif
 
 /* u32toascii ? */
 int
@@ -127,7 +153,7 @@ u32cconv (c, s)
   const char *charset;
   char obuf[25], *optr;
   size_t obytesleft;
-  char *iptr;
+  const char *iptr;
   size_t sn;
 #endif
 
@@ -154,7 +180,11 @@ u32cconv (c, s)
   /* this is mostly from coreutils-8.5/lib/unicodeio.c */
   if (u32init == 0)
     {
+#  if HAVE_LOCALE_CHARSET
       charset = locale_charset ();     /* XXX - fix later */
+#  else
+      charset = stub_charset ();
+#  endif
       if (STREQ (charset, "UTF-8"))
        utf8locale = 1;
       else
@@ -187,7 +217,7 @@ u32cconv (c, s)
 
   iconv (localconv, NULL, NULL, NULL, NULL);
 
-  if (iconv (localconv, &iptr, &sn, &optr, &obytesleft) == (size_t)-1)
+  if (iconv (localconv, (ICONV_CONST char **)&iptr, &sn, &optr, &obytesleft) == (size_t)-1)
     return n;  /* You get utf-8 if iconv fails */
 
   *optr = '\0';
index 2f2e9fc45bd7f10c38733c9c2787fd82770625e3..722a707813f62bf99f5d623ecc1f78e0170e0551 100644 (file)
--- a/locale.c
+++ b/locale.c
@@ -297,7 +297,7 @@ get_locale_var (var)
   locale = lc_all;
 
   if (locale == 0 || *locale == 0)
-    locale = get_string_value (var);
+    locale = get_string_value (var);   /* XXX - mem leak? */
   if (locale == 0 || *locale == 0)
     locale = lang;
   if (locale == 0 || *locale == 0)
diff --git a/parse.y b/parse.y
index 449f0c67d49255d5246a52a5605287dbcd219997..4fc55953f55354435a2b2a04baf4da4580eb7c74 100644 (file)
--- a/parse.y
+++ b/parse.y
@@ -5255,7 +5255,7 @@ decode_prompt_string (string)
                      {
                        t = strrchr (t_string, '/');
                        if (t)
-                         memmove (t_string, t + 1, strlen (t) - 1);
+                         memmove (t_string, t + 1, strlen (t));        /* strlen(t) to copy NULL */
                      }
                  }
 #undef ROOT_PATH
index fe03328b4d4c2497100cfcdedbfa1812e391ee7c..e4d2119b185614272091bab43dce43cd0bf15d8b 100644 (file)
--- a/parse.y~
+++ b/parse.y~
@@ -3434,7 +3434,7 @@ parse_comsub (qc, open, close, lenp, flags)
   while (count)
     {
 comsub_readchar:
-      ch = shell_getc (qc != '\'' && (tflags & LEX_PASSNEXT) == 0);
+      ch = shell_getc (qc != '\'' && (tflags & (LEX_INCOMMENT|LEX_PASSNEXT)) == 0);
 
       if (ch == EOF)
        {
@@ -5255,7 +5255,7 @@ decode_prompt_string (string)
                      {
                        t = strrchr (t_string, '/');
                        if (t)
-                         memmove (t_string, t + 1, strlen (t) - 1);
+                         memmove (t_string, t + 1, strlen (t));        /* tlen to copy NULL */
                      }
                  }
 #undef ROOT_PATH
diff --git a/sig.c b/sig.c
index 19a1002943bb53187184f6cb7ac3dc0e862dcdce..6bd1319b996751eee932dd19db4ba9de51bb4c86 100644 (file)
--- a/sig.c
+++ b/sig.c
@@ -63,7 +63,7 @@ extern int parse_and_execute_level, shell_initialized;
 extern int history_lines_this_session;
 #endif
 
-extern void intialize_siglist ();
+extern void initialize_siglist ();
 
 /* Non-zero after SIGINT. */
 volatile int interrupt_state = 0;
diff --git a/subst.c b/subst.c
index 4ad729ef6d829df11e6a38908c393e22476ac2b0..1813000b36387bc46250ad07ad07fd6be07ffac2 100644 (file)
--- a/subst.c
+++ b/subst.c
@@ -2297,11 +2297,7 @@ string_list_dollar_at (list, quoted)
 
   /* XXX -- why call quote_list if ifs == 0?  we can get away without doing
      it now that quote_escapes quotes spaces */
-#if 0
-  tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0))
-#else
   tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
-#endif
                ? quote_list (list)
                : list_quote_escapes (list);
 
@@ -2856,8 +2852,9 @@ do_assignment (string)
 }
 
 int
-do_word_assignment (word)
+do_word_assignment (word, flags)
      WORD_DESC *word;
+     int flags;
 {
   return do_assignment_internal (word, 1);
 }
@@ -4154,7 +4151,13 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
   wchar_t wc, *wp, *nwpat, *wp1;
   size_t len;
   int mlen;
-  int n, n1;
+  int n, n1, n2, simple;
+
+  simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'[');
+#if defined (EXTENDED_GLOB)
+  if (extended_glob)
+    simple |= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/
+#endif
 
   /* If the pattern doesn't match anywhere in the string, go ahead and
      short-circuit right away.  A minor optimization, saves a bunch of
@@ -4184,13 +4187,19 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
     return (0);
 
   mlen = wmatchlen (wpat, wstrlen);
+
 /* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */
   switch (mtype)
     {
     case MATCH_ANY:
       for (n = 0; n <= wstrlen; n++)
        {
-         if (match_pattern_wchar (wpat, wstring + n))
+#if 1
+         n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
+#else
+         n2 = match_pattern_wchar (wpat, wstring + n);
+#endif
+         if (n2)
            {
 #if 0
              for (n1 = wstrlen; n1 >= n; n1--)
@@ -4288,6 +4297,7 @@ match_pattern (string, pat, mtype, sp, ep)
   size_t n;
   wchar_t *wstring, *wpat;
   char **indices;
+  size_t slen, plen, mslen, mplen;
 #endif
 
   if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
@@ -4296,6 +4306,17 @@ match_pattern (string, pat, mtype, sp, ep)
 #if defined (HANDLE_MULTIBYTE)
   if (MB_CUR_MAX > 1)
     {
+#if 0
+      slen = STRLEN (string);
+      mslen = MBSLEN (string);
+      plen = STRLEN (pat);
+      mplen = MBSLEN (pat);
+      if (slen == mslen && plen == mplen)
+#else
+      if (mbsmbchar (string) == 0 && mbsmbchar (pat) == 0)
+#endif
+        return (match_upattern (string, pat, mtype, sp, ep));
+
       n = xdupmbstowcs (&wpat, NULL, pat);
       if (n == (size_t)-1)
        return (match_upattern (string, pat, mtype, sp, ep));
@@ -5855,34 +5876,6 @@ valid_length_expression (name)
          legal_identifier (name + 1));                         /* ${#PS1} */
 }
 
-#if defined (HANDLE_MULTIBYTE)
-size_t
-mbstrlen (s)
-     const char *s;
-{
-  size_t clen, nc;
-  mbstate_t mbs, mbsbak;
-
-  nc = 0;
-  memset (&mbs, 0, sizeof (mbs));
-  mbsbak = mbs;
-  while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
-    {
-      if (MB_INVALIDCH(clen))
-        {
-         clen = 1;     /* assume single byte */
-         mbs = mbsbak;
-        }
-
-      s += clen;
-      nc++;
-      mbsbak = mbs;
-    }
-  return nc;
-}
-#endif
-      
-
 /* Handle the parameter brace expansion that requires us to return the
    length of a parameter. */
 static intmax_t
@@ -5965,7 +5958,7 @@ parameter_brace_expand_length (name)
          if (list)
            dispose_words (list);
 
-         number = MB_STRLEN (t);
+         number = t ? MB_STRLEN (t) : 0;
          FREE (t);
        }
     }
@@ -7536,11 +7529,14 @@ param_expand (string, sindex, quoted, expanded_something,
             is unset, the parameters are separated by ' '; if $IFS is
             null, the parameters are concatenated. */
          temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list);
-         temp1 = quote_string (temp);
-         if (*temp == 0)
-           tflag |= W_HASQUOTEDNULL;
-         free (temp);
-         temp = temp1;
+         if (temp)
+           {
+             temp1 = quote_string (temp);
+             if (*temp == 0)
+               tflag |= W_HASQUOTEDNULL;
+             free (temp);
+             temp = temp1;
+           }
        }
       else
        {
@@ -8314,7 +8310,7 @@ add_twochars:
              if (list->next)
                {
 #if 0
-                 if (quoted_dollar_at && word->flags & W_NOSPLIT2)
+                 if (quoted_dollar_at && (word->flags & W_NOSPLIT2))
                    temp = string_list_internal (quote_list (list), " ");
                  else
 #endif
@@ -8352,12 +8348,10 @@ add_twochars:
          else
            temp = (char *)NULL;
 
-#if 0
          /* We do not want to add quoted nulls to strings that are only
             partially quoted; we can throw them away. */
-         if (temp == 0 && quoted_state == PARTIALLY_QUOTED)
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
-#endif
 
        add_quoted_string:
 
@@ -9194,7 +9188,7 @@ shell_expand_word_list (tlist, eflags)
          if (tlist->word->flags & W_ASSIGNASSOC)
            make_internal_declare (tlist->word->word, "-A");
 
-         t = do_word_assignment (tlist->word);
+         t = do_word_assignment (tlist->word, 0);
          if (t == 0)
            {
              last_command_exit_value = EXECUTION_FAILURE;
@@ -9294,7 +9288,7 @@ expand_word_list_internal (list, eflags)
              for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
                {
                  this_command_name = (char *)NULL;     /* no arithmetic errors */
-                 tint = do_word_assignment (temp_list->word);
+                 tint = do_word_assignment (temp_list->word, 0);
                  /* Variable assignment errors in non-interactive shells
                     running in Posix.2 mode cause the shell to exit. */
                  if (tint == 0)
@@ -9343,7 +9337,7 @@ expand_word_list_internal (list, eflags)
   if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
     {
       sh_wassign_func_t *assign_func;
-      int is_special_builtin;
+      int is_special_builtin, is_builtin_or_func;
 
       /* If the remainder of the words expand to nothing, Posix.2 requires
         that the variable and environment assignments affect the shell's
@@ -9351,15 +9345,16 @@ expand_word_list_internal (list, eflags)
       assign_func = new_list ? assign_in_env : do_word_assignment;
       tempenv_assign_error = 0;
 
+      is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word)));
       /* Posix says that special builtins exit if a variable assignment error
         occurs in an assignment preceding it. */
-      is_special_builtin = (posixly_correct && new_list && find_special_builtin (new_list->word->word));
+      is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word));
       
       for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
        {
          this_command_name = (char *)NULL;
          assigning_in_environment = (assign_func == assign_in_env);
-         tint = (*assign_func) (temp_list->word);
+         tint = (*assign_func) (temp_list->word, is_builtin_or_func);
          assigning_in_environment = 0;
          /* Variable assignment errors in non-interactive shells running
             in Posix.2 mode cause the shell to exit. */
diff --git a/subst.c.orig b/subst.c.orig
new file mode 100644 (file)
index 0000000..bc5781c
--- /dev/null
@@ -0,0 +1,9392 @@
+/* subst.c -- The part of the shell that does parameter, command, arithmetic,
+   and globbing substitutions. */
+
+/* ``Have a little faith, there's magic in the night.  You ain't a
+     beauty, but, hey, you're alright.'' */
+
+/* Copyright (C) 1987-2010 Free Software Foundation, Inc.
+
+   This file is part of GNU Bash, the Bourne Again SHell.
+
+   Bash is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   Bash is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with Bash.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include "bashtypes.h"
+#include <stdio.h>
+#include "chartypes.h"
+#if defined (HAVE_PWD_H)
+#  include <pwd.h>
+#endif
+#include <signal.h>
+#include <errno.h>
+
+#if defined (HAVE_UNISTD_H)
+#  include <unistd.h>
+#endif
+
+#include "bashansi.h"
+#include "posixstat.h"
+#include "bashintl.h"
+
+#include "shell.h"
+#include "parser.h"
+#include "flags.h"
+#include "jobs.h"
+#include "execute_cmd.h"
+#include "filecntl.h"
+#include "trap.h"
+#include "pathexp.h"
+#include "mailcheck.h"
+
+#include "shmbutil.h"
+#include "typemax.h"
+
+#include "builtins/getopt.h"
+#include "builtins/common.h"
+
+#include "builtins/builtext.h"
+
+#include <tilde/tilde.h>
+#include <glob/strmatch.h>
+
+#if !defined (errno)
+extern int errno;
+#endif /* !errno */
+
+/* The size that strings change by. */
+#define DEFAULT_INITIAL_ARRAY_SIZE 112
+#define DEFAULT_ARRAY_SIZE 128
+
+/* Variable types. */
+#define VT_VARIABLE    0
+#define VT_POSPARMS    1
+#define VT_ARRAYVAR    2
+#define VT_ARRAYMEMBER 3
+#define VT_ASSOCVAR    4
+
+#define VT_STARSUB     128     /* $* or ${array[*]} -- used to split */
+
+/* Flags for quoted_strchr */
+#define ST_BACKSL      0x01
+#define ST_CTLESC      0x02
+#define ST_SQUOTE      0x04    /* unused yet */
+#define ST_DQUOTE      0x08    /* unused yet */
+
+/* Flags for the `pflags' argument to param_expand() */
+#define PF_NOCOMSUB    0x01    /* Do not perform command substitution */
+#define PF_IGNUNBOUND  0x02    /* ignore unbound vars even if -u set */
+#define PF_NOSPLIT2    0x04    /* same as W_NOSPLIT2 */
+
+/* These defs make it easier to use the editor. */
+#define LBRACE         '{'
+#define RBRACE         '}'
+#define LPAREN         '('
+#define RPAREN         ')'
+
+#if defined (HANDLE_MULTIBYTE)
+#define WLPAREN                L'('
+#define WRPAREN                L')'
+#endif
+
+/* Evaluates to 1 if C is one of the shell's special parameters whose length
+   can be taken, but is also one of the special expansion characters. */
+#define VALID_SPECIAL_LENGTH_PARAM(c) \
+  ((c) == '-' || (c) == '?' || (c) == '#')
+
+/* Evaluates to 1 if C is one of the shell's special parameters for which an
+   indirect variable reference may be made. */
+#define VALID_INDIR_PARAM(c) \
+  ((posixly_correct == 0 && (c) == '#') || (posixly_correct == 0 && (c) == '?') || (c) == '@' || (c) == '*')
+
+/* Evaluates to 1 if C is one of the OP characters that follows the parameter
+   in ${parameter[:]OPword}. */
+#define VALID_PARAM_EXPAND_CHAR(c) (sh_syntaxtab[(unsigned char)c] & CSUBSTOP)
+
+/* Evaluates to 1 if this is one of the shell's special variables. */
+#define SPECIAL_VAR(name, wi) \
+ ((DIGIT (*name) && all_digits (name)) || \
+      (name[1] == '\0' && (sh_syntaxtab[(unsigned char)*name] & CSPECVAR)) || \
+      (wi && name[2] == '\0' && VALID_INDIR_PARAM (name[1])))
+
+/* An expansion function that takes a string and a quoted flag and returns
+   a WORD_LIST *.  Used as the type of the third argument to
+   expand_string_if_necessary(). */
+typedef WORD_LIST *EXPFUNC __P((char *, int));
+
+/* Process ID of the last command executed within command substitution. */
+pid_t last_command_subst_pid = NO_PID;
+pid_t current_command_subst_pid = NO_PID;
+
+/* Variables used to keep track of the characters in IFS. */
+SHELL_VAR *ifs_var;
+char *ifs_value;
+unsigned char ifs_cmap[UCHAR_MAX + 1];
+
+#if defined (HANDLE_MULTIBYTE)
+unsigned char ifs_firstc[MB_LEN_MAX];
+size_t ifs_firstc_len;
+#else
+unsigned char ifs_firstc;
+#endif
+
+/* Sentinel to tell when we are performing variable assignments preceding a
+   command name and putting them into the environment.  Used to make sure
+   we use the temporary environment when looking up variable values. */
+int assigning_in_environment;
+
+/* Used to hold a list of variable assignments preceding a command.  Global
+   so the SIGCHLD handler in jobs.c can unwind-protect it when it runs a
+   SIGCHLD trap and so it can be saved and restored by the trap handlers. */
+WORD_LIST *subst_assign_varlist = (WORD_LIST *)NULL;
+
+/* Extern functions and variables from different files. */
+extern int last_command_exit_value, last_command_exit_signal;
+extern int subshell_environment, line_number;
+extern int subshell_level, parse_and_execute_level, sourcelevel;
+extern int eof_encountered;
+extern int return_catch_flag, return_catch_value;
+extern pid_t dollar_dollar_pid;
+extern int posixly_correct;
+extern char *this_command_name;
+extern struct fd_bitmap *current_fds_to_close;
+extern int wordexp_only;
+extern int expanding_redir;
+extern int tempenv_assign_error;
+
+#if !defined (HAVE_WCSDUP) && defined (HANDLE_MULTIBYTE)
+extern wchar_t *wcsdup __P((const wchar_t *));
+#endif
+
+/* Non-zero means to allow unmatched globbed filenames to expand to
+   a null file. */
+int allow_null_glob_expansion;
+
+/* Non-zero means to throw an error when globbing fails to match anything. */
+int fail_glob_expansion;
+
+#if 0
+/* Variables to keep track of which words in an expanded word list (the
+   output of expand_word_list_internal) are the result of globbing
+   expansions.  GLOB_ARGV_FLAGS is used by execute_cmd.c.
+   (CURRENTLY UNUSED). */
+char *glob_argv_flags;
+static int glob_argv_flags_size;
+#endif
+
+static WORD_LIST expand_word_error, expand_word_fatal;
+static WORD_DESC expand_wdesc_error, expand_wdesc_fatal;
+static char expand_param_error, expand_param_fatal;
+static char extract_string_error, extract_string_fatal;
+
+/* Tell the expansion functions to not longjmp back to top_level on fatal
+   errors.  Enabled when doing completion and prompt string expansion. */
+static int no_longjmp_on_fatal_error = 0;
+
+/* Set by expand_word_unsplit; used to inhibit splitting and re-joining
+   $* on $IFS, primarily when doing assignment statements. */
+static int expand_no_split_dollar_star = 0;
+
+/* A WORD_LIST of words to be expanded by expand_word_list_internal,
+   without any leading variable assignments. */
+static WORD_LIST *garglist = (WORD_LIST *)NULL;
+
+static char *quoted_substring __P((char *, int, int));
+static int quoted_strlen __P((char *));
+static char *quoted_strchr __P((char *, int, int));
+
+static char *expand_string_if_necessary __P((char *, int, EXPFUNC *));
+static inline char *expand_string_to_string_internal __P((char *, int, EXPFUNC *));
+static WORD_LIST *call_expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
+static WORD_LIST *expand_string_internal __P((char *, int));
+static WORD_LIST *expand_string_leave_quoted __P((char *, int));
+static WORD_LIST *expand_string_for_rhs __P((char *, int, int *, int *));
+
+static WORD_LIST *list_quote_escapes __P((WORD_LIST *));
+static char *make_quoted_char __P((int));
+static WORD_LIST *quote_list __P((WORD_LIST *));
+
+static int unquoted_substring __P((char *, char *));
+static int unquoted_member __P((int, char *));
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *do_compound_assignment __P((char *, char *, int));
+#endif
+static int do_assignment_internal __P((const WORD_DESC *, int));
+
+static char *string_extract_verbatim __P((char *, size_t, int *, char *, int));
+static char *string_extract __P((char *, int *, char *, int));
+static char *string_extract_double_quoted __P((char *, int *, int));
+static inline char *string_extract_single_quoted __P((char *, int *));
+static inline int skip_single_quoted __P((const char *, size_t, int));
+static int skip_double_quoted __P((char *, size_t, int));
+static char *extract_delimited_string __P((char *, int *, char *, char *, char *, int));
+static char *extract_dollar_brace_string __P((char *, int *, int, int));
+static int skip_matched_pair __P((const char *, int, int, int, int));
+
+static char *pos_params __P((char *, int, int, int));
+
+static unsigned char *mb_getcharlens __P((char *, int));
+
+static char *remove_upattern __P((char *, char *, int));
+#if defined (HANDLE_MULTIBYTE) 
+static wchar_t *remove_wpattern __P((wchar_t *, size_t, wchar_t *, int));
+#endif
+static char *remove_pattern __P((char *, char *, int));
+
+static int match_upattern __P((char *, char *, int, char **, char **));
+#if defined (HANDLE_MULTIBYTE)
+static int match_wpattern __P((wchar_t *, char **, size_t, wchar_t *, int, char **, char **));
+#endif
+static int match_pattern __P((char *, char *, int, char **, char **));
+static int getpatspec __P((int, char *));
+static char *getpattern __P((char *, int, int));
+static char *variable_remove_pattern __P((char *, char *, int, int));
+static char *list_remove_pattern __P((WORD_LIST *, char *, int, int, int));
+static char *parameter_list_remove_pattern __P((int, char *, int, int));
+#ifdef ARRAY_VARS
+static char *array_remove_pattern __P((SHELL_VAR *, char *, int, char *, int));
+#endif
+static char *parameter_brace_remove_pattern __P((char *, char *, int, char *, int, int, int));
+
+static char *process_substitute __P((char *, int));
+
+static char *read_comsub __P((int, int, int *));
+
+#ifdef ARRAY_VARS
+static arrayind_t array_length_reference __P((char *));
+#endif
+
+static int valid_brace_expansion_word __P((char *, int));
+static int chk_atstar __P((char *, int, int *, int *));
+static int chk_arithsub __P((const char *, int));
+
+static WORD_DESC *parameter_brace_expand_word __P((char *, int, int, int, arrayind_t *));
+static WORD_DESC *parameter_brace_expand_indir __P((char *, int, int, int *, int *));
+static WORD_DESC *parameter_brace_expand_rhs __P((char *, char *, int, int, int *, int *));
+static void parameter_brace_expand_error __P((char *, char *));
+
+static int valid_length_expression __P((char *));
+static intmax_t parameter_brace_expand_length __P((char *));
+
+static char *skiparith __P((char *, int));
+static int verify_substring_values __P((SHELL_VAR *, char *, char *, int, intmax_t *, intmax_t *));
+static int get_var_and_type __P((char *, char *, arrayind_t, int, int, SHELL_VAR **, char **));
+static char *mb_substring __P((char *, int, int));
+static char *parameter_brace_substring __P((char *, char *, int, char *, int, int));
+
+static int shouldexp_replacement __P((char *));
+
+static char *pos_params_pat_subst __P((char *, char *, char *, int));
+
+static char *parameter_brace_patsub __P((char *, char *, int, char *, int, int));
+
+static char *pos_params_casemod __P((char *, char *, int, int));
+static char *parameter_brace_casemod __P((char *, char *, int, int, char *, int, int));
+
+static WORD_DESC *parameter_brace_expand __P((char *, int *, int, int, int *, int *));
+static WORD_DESC *param_expand __P((char *, int *, int, int *, int *, int *, int *, int));
+
+static WORD_LIST *expand_word_internal __P((WORD_DESC *, int, int, int *, int *));
+
+static WORD_LIST *word_list_split __P((WORD_LIST *));
+
+static void exp_jump_to_top_level __P((int));
+
+static WORD_LIST *separate_out_assignments __P((WORD_LIST *));
+static WORD_LIST *glob_expand_word_list __P((WORD_LIST *, int));
+#ifdef BRACE_EXPANSION
+static WORD_LIST *brace_expand_word_list __P((WORD_LIST *, int));
+#endif
+#if defined (ARRAY_VARS)
+static int make_internal_declare __P((char *, char *));
+#endif
+static WORD_LIST *shell_expand_word_list __P((WORD_LIST *, int));
+static WORD_LIST *expand_word_list_internal __P((WORD_LIST *, int));
+
+/* **************************************************************** */
+/*                                                                 */
+/*                     Utility Functions                           */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (DEBUG)
+void
+dump_word_flags (flags)
+     int flags;
+{
+  int f;
+
+  f = flags;
+  fprintf (stderr, "%d -> ", f);
+  if (f & W_ASSIGNASSOC)
+    {
+      f &= ~W_ASSIGNASSOC;
+      fprintf (stderr, "W_ASSIGNASSOC%s", f ? "|" : "");
+    }
+  if (f & W_HASCTLESC)
+    {
+      f &= ~W_HASCTLESC;
+      fprintf (stderr, "W_HASCTLESC%s", f ? "|" : "");
+    }
+  if (f & W_NOPROCSUB)
+    {
+      f &= ~W_NOPROCSUB;
+      fprintf (stderr, "W_NOPROCSUB%s", f ? "|" : "");
+    }
+  if (f & W_DQUOTE)
+    {
+      f &= ~W_DQUOTE;
+      fprintf (stderr, "W_DQUOTE%s", f ? "|" : "");
+    }
+  if (f & W_HASQUOTEDNULL)
+    {
+      f &= ~W_HASQUOTEDNULL;
+      fprintf (stderr, "W_HASQUOTEDNULL%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNARG)
+    {
+      f &= ~W_ASSIGNARG;
+      fprintf (stderr, "W_ASSIGNARG%s", f ? "|" : "");
+    }
+  if (f & W_ASSNBLTIN)
+    {
+      f &= ~W_ASSNBLTIN;
+      fprintf (stderr, "W_ASSNBLTIN%s", f ? "|" : "");
+    }
+  if (f & W_COMPASSIGN)
+    {
+      f &= ~W_COMPASSIGN;
+      fprintf (stderr, "W_COMPASSIGN%s", f ? "|" : "");
+    }
+  if (f & W_NOEXPAND)
+    {
+      f &= ~W_NOEXPAND;
+      fprintf (stderr, "W_NOEXPAND%s", f ? "|" : "");
+    }
+  if (f & W_ITILDE)
+    {
+      f &= ~W_ITILDE;
+      fprintf (stderr, "W_ITILDE%s", f ? "|" : "");
+    }
+  if (f & W_NOTILDE)
+    {
+      f &= ~W_NOTILDE;
+      fprintf (stderr, "W_NOTILDE%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNRHS)
+    {
+      f &= ~W_ASSIGNRHS;
+      fprintf (stderr, "W_ASSIGNRHS%s", f ? "|" : "");
+    }
+  if (f & W_NOCOMSUB)
+    {
+      f &= ~W_NOCOMSUB;
+      fprintf (stderr, "W_NOCOMSUB%s", f ? "|" : "");
+    }
+  if (f & W_DOLLARSTAR)
+    {
+      f &= ~W_DOLLARSTAR;
+      fprintf (stderr, "W_DOLLARSTAR%s", f ? "|" : "");
+    }
+  if (f & W_DOLLARAT)
+    {
+      f &= ~W_DOLLARAT;
+      fprintf (stderr, "W_DOLLARAT%s", f ? "|" : "");
+    }
+  if (f & W_TILDEEXP)
+    {
+      f &= ~W_TILDEEXP;
+      fprintf (stderr, "W_TILDEEXP%s", f ? "|" : "");
+    }
+  if (f & W_NOSPLIT2)
+    {
+      f &= ~W_NOSPLIT2;
+      fprintf (stderr, "W_NOSPLIT2%s", f ? "|" : "");
+    }
+  if (f & W_NOGLOB)
+    {
+      f &= ~W_NOGLOB;
+      fprintf (stderr, "W_NOGLOB%s", f ? "|" : "");
+    }
+  if (f & W_NOSPLIT)
+    {
+      f &= ~W_NOSPLIT;
+      fprintf (stderr, "W_NOSPLIT%s", f ? "|" : "");
+    }
+  if (f & W_GLOBEXP)
+    {
+      f &= ~W_GLOBEXP;
+      fprintf (stderr, "W_GLOBEXP%s", f ? "|" : "");
+    }
+  if (f & W_ASSIGNMENT)
+    {
+      f &= ~W_ASSIGNMENT;
+      fprintf (stderr, "W_ASSIGNMENT%s", f ? "|" : "");
+    }
+  if (f & W_QUOTED)
+    {
+      f &= ~W_QUOTED;
+      fprintf (stderr, "W_QUOTED%s", f ? "|" : "");
+    }
+  if (f & W_HASDOLLAR)
+    {
+      f &= ~W_HASDOLLAR;
+      fprintf (stderr, "W_HASDOLLAR%s", f ? "|" : "");
+    }
+  fprintf (stderr, "\n");
+  fflush (stderr);
+}
+#endif
+
+#ifdef INCLUDE_UNUSED
+static char *
+quoted_substring (string, start, end)
+     char *string;
+     int start, end;
+{
+  register int len, l;
+  register char *result, *s, *r;
+
+  len = end - start;
+
+  /* Move to string[start], skipping quoted characters. */
+  for (s = string, l = 0; *s && l < start; )
+    {
+      if (*s == CTLESC)
+       {
+         s++;
+         continue;
+       }
+      l++;
+      if (*s == 0)
+       break;
+    }
+
+  r = result = (char *)xmalloc (2*len + 1);      /* save room for quotes */
+
+  /* Copy LEN characters, including quote characters. */
+  s = string + l;
+  for (l = 0; l < len; s++)
+    {
+      if (*s == CTLESC)
+       *r++ = *s++;
+      *r++ = *s;
+      l++;
+      if (*s == 0)
+       break;
+    }
+  *r = '\0';
+  return result;
+}
+#endif
+
+#ifdef INCLUDE_UNUSED
+/* Return the length of S, skipping over quoted characters */
+static int
+quoted_strlen (s)
+     char *s;
+{
+  register char *p;
+  int i;
+
+  i = 0;
+  for (p = s; *p; p++)
+    {
+      if (*p == CTLESC)
+       {
+         p++;
+         if (*p == 0)
+           return (i + 1);
+       }
+      i++;
+    }
+
+  return i;
+}
+#endif
+
+/* Find the first occurrence of character C in string S, obeying shell
+   quoting rules.  If (FLAGS & ST_BACKSL) is non-zero, backslash-escaped
+   characters are skipped.  If (FLAGS & ST_CTLESC) is non-zero, characters
+   escaped with CTLESC are skipped. */
+static char *
+quoted_strchr (s, c, flags)
+     char *s;
+     int c, flags;
+{
+  register char *p;
+
+  for (p = s; *p; p++)
+    {
+      if (((flags & ST_BACKSL) && *p == '\\')
+           || ((flags & ST_CTLESC) && *p == CTLESC))
+       {
+         p++;
+         if (*p == '\0')
+           return ((char *)NULL);
+         continue;
+       }
+      else if (*p == c)
+       return p;
+    }
+  return ((char *)NULL);
+}
+
+/* Return 1 if CHARACTER appears in an unquoted portion of
+   STRING.  Return 0 otherwise.  CHARACTER must be a single-byte character. */
+static int
+unquoted_member (character, string)
+     int character;
+     char *string;
+{
+  size_t slen;
+  int sindex, c;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  sindex = 0;
+  while (c = string[sindex])
+    {
+      if (c == character)
+       return (1);
+
+      switch (c)
+       {
+       default:
+         ADVANCE_CHAR (string, slen, sindex);
+         break;
+
+       case '\\':
+         sindex++;
+         if (string[sindex])
+           ADVANCE_CHAR (string, slen, sindex);
+         break;
+
+       case '\'':
+         sindex = skip_single_quoted (string, slen, ++sindex);
+         break;
+
+       case '"':
+         sindex = skip_double_quoted (string, slen, ++sindex);
+         break;
+       }
+    }
+  return (0);
+}
+
+/* Return 1 if SUBSTR appears in an unquoted portion of STRING. */
+static int
+unquoted_substring (substr, string)
+     char *substr, *string;
+{
+  size_t slen;
+  int sindex, c, sublen;
+  DECLARE_MBSTATE;
+
+  if (substr == 0 || *substr == '\0')
+    return (0);
+
+  slen = strlen (string);
+  sublen = strlen (substr);
+  for (sindex = 0; c = string[sindex]; )
+    {
+      if (STREQN (string + sindex, substr, sublen))
+       return (1);
+
+      switch (c)
+       {
+       case '\\':
+         sindex++;
+         if (string[sindex])
+           ADVANCE_CHAR (string, slen, sindex);
+         break;
+
+       case '\'':
+         sindex = skip_single_quoted (string, slen, ++sindex);
+         break;
+
+       case '"':
+         sindex = skip_double_quoted (string, slen, ++sindex);
+         break;
+
+       default:
+         ADVANCE_CHAR (string, slen, sindex);
+         break;
+       }
+    }
+  return (0);
+}
+
+/* Most of the substitutions must be done in parallel.  In order
+   to avoid using tons of unclear goto's, I have some functions
+   for manipulating malloc'ed strings.  They all take INDX, a
+   pointer to an integer which is the offset into the string
+   where manipulation is taking place.  They also take SIZE, a
+   pointer to an integer which is the current length of the
+   character array for this string. */
+
+/* Append SOURCE to TARGET at INDEX.  SIZE is the current amount
+   of space allocated to TARGET.  SOURCE can be NULL, in which
+   case nothing happens.  Gets rid of SOURCE by freeing it.
+   Returns TARGET in case the location has changed. */
+INLINE char *
+sub_append_string (source, target, indx, size)
+     char *source, *target;
+     int *indx, *size;
+{
+  if (source)
+    {
+      int srclen, n;
+
+      srclen = STRLEN (source);
+      if (srclen >= (int)(*size - *indx))
+       {
+         n = srclen + *indx;
+         n = (n + DEFAULT_ARRAY_SIZE) - (n % DEFAULT_ARRAY_SIZE);
+         target = (char *)xrealloc (target, (*size = n));
+       }
+
+      FASTCOPY (source, target + *indx, srclen);
+      *indx += srclen;
+      target[*indx] = '\0';
+
+      free (source);
+    }
+  return (target);
+}
+
+#if 0
+/* UNUSED */
+/* Append the textual representation of NUMBER to TARGET.
+   INDX and SIZE are as in SUB_APPEND_STRING. */
+char *
+sub_append_number (number, target, indx, size)
+     intmax_t number;
+     int *indx, *size;
+     char *target;
+{
+  char *temp;
+
+  temp = itos (number);
+  return (sub_append_string (temp, target, indx, size));
+}
+#endif
+
+/* Extract a substring from STRING, starting at SINDEX and ending with
+   one of the characters in CHARLIST.  Don't make the ending character
+   part of the string.  Leave SINDEX pointing at the ending character.
+   Understand about backslashes in the string.  If (flags & SX_VARNAME)
+   is non-zero, and array variables have been compiled into the shell,
+   everything between a `[' and a corresponding `]' is skipped over.
+   If (flags & SX_NOALLOC) is non-zero, don't return the substring, just
+   update SINDEX.  If (flags & SX_REQMATCH) is non-zero, the string must
+   contain a closing character from CHARLIST. */
+static char *
+string_extract (string, sindex, charlist, flags)
+     char *string;
+     int *sindex;
+     char *charlist;
+     int flags;
+{
+  register int c, i;
+  int found;
+  size_t slen;
+  char *temp;
+  DECLARE_MBSTATE;
+
+  slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0;
+  i = *sindex;
+  found = 0;
+  while (c = string[i])
+    {
+      if (c == '\\')
+       {
+         if (string[i + 1])
+           i++;
+         else
+           break;
+       }
+#if defined (ARRAY_VARS)
+      else if ((flags & SX_VARNAME) && c == '[')
+       {
+         int ni;
+         /* If this is an array subscript, skip over it and continue. */
+         ni = skipsubscript (string, i, 0);
+         if (string[ni] == ']')
+           i = ni;
+       }
+#endif
+      else if (MEMBER (c, charlist))
+       {
+         found = 1;
+         break;
+       }
+
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  /* If we had to have a matching delimiter and didn't find one, return an
+     error and let the caller deal with it. */
+  if ((flags & SX_REQMATCH) && found == 0)
+    {
+      *sindex = i;
+      return (&extract_string_error);
+    }
+  
+  temp = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i);
+  *sindex = i;
+  
+  return (temp);
+}
+
+/* Extract the contents of STRING as if it is enclosed in double quotes.
+   SINDEX, when passed in, is the offset of the character immediately
+   following the opening double quote; on exit, SINDEX is left pointing after
+   the closing double quote.  If STRIPDQ is non-zero, unquoted double
+   quotes are stripped and the string is terminated by a null byte.
+   Backslashes between the embedded double quotes are processed.  If STRIPDQ
+   is zero, an unquoted `"' terminates the string. */
+static char *
+string_extract_double_quoted (string, sindex, stripdq)
+     char *string;
+     int *sindex, stripdq;
+{
+  size_t slen;
+  char *send;
+  int j, i, t;
+  unsigned char c;
+  char *temp, *ret;            /* The new string we return. */
+  int pass_next, backquote, si;        /* State variables for the machine. */
+  int dquote;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + *sindex) + *sindex;
+  send = string + slen;
+
+  pass_next = backquote = dquote = 0;
+  temp = (char *)xmalloc (1 + slen - *sindex);
+
+  j = 0;
+  i = *sindex;
+  while (c = string[i])
+    {
+      /* Process a character that was quoted by a backslash. */
+      if (pass_next)
+       {
+         /* XXX - take another look at this in light of Interp 221 */
+         /* Posix.2 sez:
+
+            ``The backslash shall retain its special meaning as an escape
+            character only when followed by one of the characters:
+               $       `       "       \       <newline>''.
+
+            If STRIPDQ is zero, we handle the double quotes here and let
+            expand_word_internal handle the rest.  If STRIPDQ is non-zero,
+            we have already been through one round of backslash stripping,
+            and want to strip these backslashes only if DQUOTE is non-zero,
+            indicating that we are inside an embedded double-quoted string. */
+
+            /* If we are in an embedded quoted string, then don't strip
+               backslashes before characters for which the backslash
+               retains its special meaning, but remove backslashes in
+               front of other characters.  If we are not in an
+               embedded quoted string, don't strip backslashes at all.
+               This mess is necessary because the string was already
+               surrounded by double quotes (and sh has some really weird
+               quoting rules).
+               The returned string will be run through expansion as if
+               it were double-quoted. */
+         if ((stripdq == 0 && c != '"') ||
+             (stripdq && ((dquote && (sh_syntaxtab[c] & CBSDQUOTE)) || dquote == 0)))
+           temp[j++] = '\\';
+         pass_next = 0;
+
+add_one_character:
+         COPY_CHAR_I (temp, j, string, send, i);
+         continue;
+       }
+
+      /* A backslash protects the next character.  The code just above
+        handles preserving the backslash in front of any character but
+        a double quote. */
+      if (c == '\\')
+       {
+         pass_next++;
+         i++;
+         continue;
+       }
+
+      /* Inside backquotes, ``the portion of the quoted string from the
+        initial backquote and the characters up to the next backquote
+        that is not preceded by a backslash, having escape characters
+        removed, defines that command''. */
+      if (backquote)
+       {
+         if (c == '`')
+           backquote = 0;
+         temp[j++] = c;
+         i++;
+         continue;
+       }
+
+      if (c == '`')
+       {
+         temp[j++] = c;
+         backquote++;
+         i++;
+         continue;
+       }
+
+      /* Pass everything between `$(' and the matching `)' or a quoted
+        ${ ... } pair through according to the Posix.2 specification. */
+      if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE)))
+       {
+         int free_ret = 1;
+
+         si = i + 2;
+         if (string[i + 1] == LPAREN)
+           ret = extract_command_subst (string, &si, 0);
+         else
+           ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, 0);
+
+         temp[j++] = '$';
+         temp[j++] = string[i + 1];
+
+         /* Just paranoia; ret will not be 0 unless no_longjmp_on_fatal_error
+            is set. */
+         if (ret == 0 && no_longjmp_on_fatal_error)
+           {
+             free_ret = 0;
+             ret = string + i + 2;
+           }
+
+         for (t = 0; ret[t]; t++, j++)
+           temp[j] = ret[t];
+         temp[j] = string[si];
+
+         if (string[si])
+           {
+             j++;
+             i = si + 1;
+           }
+         else
+           i = si;
+
+         if (free_ret)
+           free (ret);
+         continue;
+       }
+
+      /* Add any character but a double quote to the quoted string we're
+        accumulating. */
+      if (c != '"')
+       goto add_one_character;
+
+      /* c == '"' */
+      if (stripdq)
+       {
+         dquote ^= 1;
+         i++;
+         continue;
+       }
+
+      break;
+    }
+  temp[j] = '\0';
+
+  /* Point to after the closing quote. */
+  if (c)
+    i++;
+  *sindex = i;
+
+  return (temp);
+}
+
+/* This should really be another option to string_extract_double_quoted. */
+static int
+skip_double_quoted (string, slen, sind)
+     char *string;
+     size_t slen;
+     int sind;
+{
+  int c, i;
+  char *ret;
+  int pass_next, backquote, si;
+  DECLARE_MBSTATE;
+
+  pass_next = backquote = 0;
+  i = sind;
+  while (c = string[i])
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next++;
+         i++;
+         continue;
+       }
+      else if (backquote)
+       {
+         if (c == '`')
+           backquote = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '`')
+       {
+         backquote++;
+         i++;
+         continue;
+       }
+      else if (c == '$' && ((string[i + 1] == LPAREN) || (string[i + 1] == LBRACE)))
+       {
+         si = i + 2;
+         if (string[i + 1] == LPAREN)
+           ret = extract_command_subst (string, &si, SX_NOALLOC);
+         else
+           ret = extract_dollar_brace_string (string, &si, Q_DOUBLE_QUOTES, SX_NOALLOC);
+
+         i = si + 1;
+         continue;
+       }
+      else if (c != '"')
+       {
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else
+       break;
+    }
+
+  if (c)
+    i++;
+
+  return (i);
+}
+
+/* Extract the contents of STRING as if it is enclosed in single quotes.
+   SINDEX, when passed in, is the offset of the character immediately
+   following the opening single quote; on exit, SINDEX is left pointing after
+   the closing single quote. */
+static inline char *
+string_extract_single_quoted (string, sindex)
+     char *string;
+     int *sindex;
+{
+  register int i;
+  size_t slen;
+  char *t;
+  DECLARE_MBSTATE;
+
+  /* Don't need slen for ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 0;
+  i = *sindex;
+  while (string[i] && string[i] != '\'')
+    ADVANCE_CHAR (string, slen, i);
+
+  t = substring (string, *sindex, i);
+
+  if (string[i])
+    i++;
+  *sindex = i;
+
+  return (t);
+}
+
+static inline int
+skip_single_quoted (string, slen, sind)
+     const char *string;
+     size_t slen;
+     int sind;
+{
+  register int c;
+  DECLARE_MBSTATE;
+
+  c = sind;
+  while (string[c] && string[c] != '\'')
+    ADVANCE_CHAR (string, slen, c);
+
+  if (string[c])
+    c++;
+  return c;
+}
+
+/* Just like string_extract, but doesn't hack backslashes or any of
+   that other stuff.  Obeys CTLESC quoting.  Used to do splitting on $IFS. */
+static char *
+string_extract_verbatim (string, slen, sindex, charlist, flags)
+     char *string;
+     size_t slen;
+     int *sindex;
+     char *charlist;
+     int flags;
+{
+  register int i;
+#if defined (HANDLE_MULTIBYTE)
+  size_t clen;
+  wchar_t *wcharlist;
+#endif
+  int c;
+  char *temp;
+  DECLARE_MBSTATE;
+
+  if (charlist[0] == '\'' && charlist[1] == '\0')
+    {
+      temp = string_extract_single_quoted (string, sindex);
+      --*sindex;       /* leave *sindex at separator character */
+      return temp;
+    }
+
+  i = *sindex;
+#if 0
+  /* See how the MBLEN and ADVANCE_CHAR macros work to understand why we need
+     this only if MB_CUR_MAX > 1. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string + *sindex) + *sindex : 1;
+#endif
+#if defined (HANDLE_MULTIBYTE)
+  clen = strlen (charlist);
+  wcharlist = 0;
+#endif
+  while (c = string[i])
+    {
+#if defined (HANDLE_MULTIBYTE)
+      size_t mblength;
+#endif
+      if ((flags & SX_NOCTLESC) == 0 && c == CTLESC)
+       {
+         i += 2;
+         continue;
+       }
+      /* Even if flags contains SX_NOCTLESC, we let CTLESC quoting CTLNUL
+        through, to protect the CTLNULs from later calls to
+        remove_quoted_nulls. */
+      else if ((flags & SX_NOESCCTLNUL) == 0 && c == CTLESC && string[i+1] == CTLNUL)
+       {
+         i += 2;
+         continue;
+       }
+
+#if defined (HANDLE_MULTIBYTE)
+      mblength = MBLEN (string + i, slen - i);
+      if (mblength > 1)
+       {
+         wchar_t wc;
+         mblength = mbtowc (&wc, string + i, slen - i);
+         if (MB_INVALIDCH (mblength))
+           {
+             if (MEMBER (c, charlist))
+               break;
+           }
+         else
+           {
+             if (wcharlist == 0)
+               {
+                 size_t len;
+                 len = mbstowcs (wcharlist, charlist, 0);
+                 if (len == -1)
+                   len = 0;
+                 wcharlist = (wchar_t *)xmalloc (sizeof (wchar_t) * (len + 1));
+                 mbstowcs (wcharlist, charlist, len + 1);
+               }
+
+             if (wcschr (wcharlist, wc))
+               break;
+           }
+       }
+      else             
+#endif
+      if (MEMBER (c, charlist))
+       break;
+
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+#if defined (HANDLE_MULTIBYTE)
+  FREE (wcharlist);
+#endif
+
+  temp = substring (string, *sindex, i);
+  *sindex = i;
+
+  return (temp);
+}
+
+/* Extract the $( construct in STRING, and return a new string.
+   Start extracting at (SINDEX) as if we had just seen "$(".
+   Make (SINDEX) get the position of the matching ")". )
+   XFLAGS is additional flags to pass to other extraction functions. */
+char *
+extract_command_subst (string, sindex, xflags)
+     char *string;
+     int *sindex;
+     int xflags;
+{
+  if (string[*sindex] == LPAREN)
+    return (extract_delimited_string (string, sindex, "$(", "(", ")", xflags|SX_COMMAND)); /*)*/
+  else
+    {
+      xflags |= (no_longjmp_on_fatal_error ? SX_NOLONGJMP : 0);
+      return (xparse_dolparen (string, string+*sindex, sindex, xflags));
+    }
+}
+
+/* Extract the $[ construct in STRING, and return a new string. (])
+   Start extracting at (SINDEX) as if we had just seen "$[".
+   Make (SINDEX) get the position of the matching "]". */
+char *
+extract_arithmetic_subst (string, sindex)
+     char *string;
+     int *sindex;
+{
+  return (extract_delimited_string (string, sindex, "$[", "[", "]", 0)); /*]*/
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+/* Extract the <( or >( construct in STRING, and return a new string.
+   Start extracting at (SINDEX) as if we had just seen "<(".
+   Make (SINDEX) get the position of the matching ")". */ /*))*/
+char *
+extract_process_subst (string, starter, sindex)
+     char *string;
+     char *starter;
+     int *sindex;
+{
+  return (extract_delimited_string (string, sindex, starter, "(", ")", 0));
+}
+#endif /* PROCESS_SUBSTITUTION */
+
+#if defined (ARRAY_VARS)
+/* This can be fooled by unquoted right parens in the passed string. If
+   each caller verifies that the last character in STRING is a right paren,
+   we don't even need to call extract_delimited_string. */
+char *
+extract_array_assignment_list (string, sindex)
+     char *string;
+     int *sindex;
+{
+  int slen;
+  char *ret;
+
+  slen = strlen (string);      /* ( */
+  if (string[slen - 1] == ')')
+   {
+      ret = substring (string, *sindex, slen - 1);
+      *sindex = slen - 1;
+      return ret;
+    }
+  return 0;  
+}
+#endif
+
+/* Extract and create a new string from the contents of STRING, a
+   character string delimited with OPENER and CLOSER.  SINDEX is
+   the address of an int describing the current offset in STRING;
+   it should point to just after the first OPENER found.  On exit,
+   SINDEX gets the position of the last character of the matching CLOSER.
+   If OPENER is more than a single character, ALT_OPENER, if non-null,
+   contains a character string that can also match CLOSER and thus
+   needs to be skipped. */
+static char *
+extract_delimited_string (string, sindex, opener, alt_opener, closer, flags)
+     char *string;
+     int *sindex;
+     char *opener, *alt_opener, *closer;
+     int flags;
+{
+  int i, c, si;
+  size_t slen;
+  char *t, *result;
+  int pass_character, nesting_level, in_comment;
+  int len_closer, len_opener, len_alt_opener;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + *sindex) + *sindex;
+  len_opener = STRLEN (opener);
+  len_alt_opener = STRLEN (alt_opener);
+  len_closer = STRLEN (closer);
+
+  pass_character = in_comment = 0;
+
+  nesting_level = 1;
+  i = *sindex;
+
+  while (nesting_level)
+    {
+      c = string[i];
+
+      if (c == 0)
+       break;
+
+      if (in_comment)
+       {
+         if (c == '\n')
+           in_comment = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+
+      if (pass_character)      /* previous char was backslash */
+       {
+         pass_character = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+
+      /* Not exactly right yet; should handle shell metacharacters and
+        multibyte characters, too.  See COMMENT_BEGIN define in parse.y */
+      if ((flags & SX_COMMAND) && c == '#' && (i == 0 || string[i - 1] == '\n' || shellblank (string[i - 1])))
+       {
+          in_comment = 1;
+          ADVANCE_CHAR (string, slen, i);
+          continue;
+       }
+        
+      if (c == CTLESC || c == '\\')
+       {
+         pass_character++;
+         i++;
+         continue;
+       }
+
+      /* Process a nested command substitution, but only if we're parsing an
+        arithmetic substitution. */
+      if ((flags & SX_COMMAND) && string[i] == '$' && string[i+1] == LPAREN)
+        {
+          si = i + 2;
+          t = extract_command_subst (string, &si, flags|SX_NOALLOC);
+          i = si + 1;
+          continue;
+        }
+
+      /* Process a nested OPENER. */
+      if (STREQN (string + i, opener, len_opener))
+       {
+         si = i + len_opener;
+         t = extract_delimited_string (string, &si, opener, alt_opener, closer, flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* Process a nested ALT_OPENER */
+      if (len_alt_opener && STREQN (string + i, alt_opener, len_alt_opener))
+       {
+         si = i + len_alt_opener;
+         t = extract_delimited_string (string, &si, alt_opener, alt_opener, closer, flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* If the current substring terminates the delimited string, decrement
+        the nesting level. */
+      if (STREQN (string + i, closer, len_closer))
+       {
+         i += len_closer - 1;  /* move to last byte of the closer */
+         nesting_level--;
+         if (nesting_level == 0)
+           break;
+       }
+
+      /* Pass old-style command substitution through verbatim. */
+      if (c == '`')
+       {
+         si = i + 1;
+         t = string_extract (string, &si, "`", flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* Pass single-quoted and double-quoted strings through verbatim. */
+      if (c == '\'' || c == '"')
+       {
+         si = i + 1;
+         i = (c == '\'') ? skip_single_quoted (string, slen, si)
+                         : skip_double_quoted (string, slen, si);
+         continue;
+       }
+
+      /* move past this character, which was not special. */
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  if (c == 0 && nesting_level)
+    {
+      if (no_longjmp_on_fatal_error == 0)
+       {
+         report_error (_("bad substitution: no closing `%s' in %s"), closer, string);
+         last_command_exit_value = EXECUTION_FAILURE;
+         exp_jump_to_top_level (DISCARD);
+       }
+      else
+       {
+         *sindex = i;
+         return (char *)NULL;
+       }
+    }
+
+  si = i - *sindex - len_closer + 1;
+  if (flags & SX_NOALLOC)
+    result = (char *)NULL;
+  else    
+    {
+      result = (char *)xmalloc (1 + si);
+      strncpy (result, string + *sindex, si);
+      result[si] = '\0';
+    }
+  *sindex = i;
+
+  return (result);
+}
+
+/* Extract a parameter expansion expression within ${ and } from STRING.
+   Obey the Posix.2 rules for finding the ending `}': count braces while
+   skipping over enclosed quoted strings and command substitutions.
+   SINDEX is the address of an int describing the current offset in STRING;
+   it should point to just after the first `{' found.  On exit, SINDEX
+   gets the position of the matching `}'.  QUOTED is non-zero if this
+   occurs inside double quotes. */
+/* XXX -- this is very similar to extract_delimited_string -- XXX */
+static char *
+extract_dollar_brace_string (string, sindex, quoted, flags)
+     char *string;
+     int *sindex, quoted, flags;
+{
+  register int i, c;
+  size_t slen;
+  int pass_character, nesting_level, si, dolbrace_state;
+  char *result, *t;
+  DECLARE_MBSTATE;
+
+  pass_character = 0;
+  nesting_level = 1;
+  slen = strlen (string + *sindex) + *sindex;
+
+  /* The handling of dolbrace_state needs to agree with the code in parse.y:
+     parse_matched_pair() */
+  dolbrace_state = 0;
+  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    dolbrace_state = (flags & SX_POSIXEXP) ? DOLBRACE_QUOTE : DOLBRACE_PARAM;
+
+  i = *sindex;
+  while (c = string[i])
+    {
+      if (pass_character)
+       {
+         pass_character = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+
+      /* CTLESCs and backslashes quote the next character. */
+      if (c == CTLESC || c == '\\')
+       {
+         pass_character++;
+         i++;
+         continue;
+       }
+
+      if (string[i] == '$' && string[i+1] == LBRACE)
+       {
+         nesting_level++;
+         i += 2;
+         continue;
+       }
+
+      if (c == RBRACE)
+       {
+         nesting_level--;
+         if (nesting_level == 0)
+           break;
+         i++;
+         continue;
+       }
+
+      /* Pass the contents of old-style command substitutions through
+        verbatim. */
+      if (c == '`')
+       {
+         si = i + 1;
+         t = string_extract (string, &si, "`", flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+      /* Pass the contents of new-style command substitutions and
+        arithmetic substitutions through verbatim. */
+      if (string[i] == '$' && string[i+1] == LPAREN)
+       {
+         si = i + 2;
+         t = extract_command_subst (string, &si, flags|SX_NOALLOC);
+         i = si + 1;
+         continue;
+       }
+
+#if 0
+      /* Pass the contents of single-quoted and double-quoted strings
+        through verbatim. */
+      if (c == '\'' || c == '"')
+       {
+         si = i + 1;
+         i = (c == '\'') ? skip_single_quoted (string, slen, si)
+                         : skip_double_quoted (string, slen, si);
+         /* skip_XXX_quoted leaves index one past close quote */
+         continue;
+       }
+#else  /* XXX - bash-4.2 */
+      /* Pass the contents of double-quoted strings through verbatim. */
+      if (c == '"')
+       {
+         si = i + 1;
+         i = skip_double_quoted (string, slen, si);
+         /* skip_XXX_quoted leaves index one past close quote */
+         continue;
+       }
+
+      if (c == '\'')
+       {
+/*itrace("extract_dollar_brace_string: c == single quote flags = %d quoted = %d dolbrace_state = %d", flags, quoted, dolbrace_state);*/
+         if (posixly_correct && shell_compatibility_level > 41 && dolbrace_state != DOLBRACE_QUOTE && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+           ADVANCE_CHAR (string, slen, i);
+         else
+           {
+             si = i + 1;
+             i = skip_single_quoted (string, slen, si);
+           }
+
+          continue;
+       }
+#endif
+
+      /* move past this character, which was not special. */
+      ADVANCE_CHAR (string, slen, i);
+
+      /* This logic must agree with parse.y:parse_matched_pair, since they
+        share the same defines. */
+      if (dolbrace_state == DOLBRACE_PARAM && c == '%' && (i - *sindex) > 1)
+       dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '#' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '/' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == '^' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && c == ',' && (i - *sindex) > 1)
+        dolbrace_state = DOLBRACE_QUOTE;
+      else if (dolbrace_state == DOLBRACE_PARAM && strchr ("#%^,~:-=?+/", c) != 0)
+       dolbrace_state = DOLBRACE_OP;
+      else if (dolbrace_state == DOLBRACE_OP && strchr ("#%^,~:-=?+/", c) == 0)
+       dolbrace_state = DOLBRACE_WORD;
+    }
+
+  if (c == 0 && nesting_level)
+    {
+      if (no_longjmp_on_fatal_error == 0)
+       {                       /* { */
+         report_error (_("bad substitution: no closing `%s' in %s"), "}", string);
+         last_command_exit_value = EXECUTION_FAILURE;
+         exp_jump_to_top_level (DISCARD);
+       }
+      else
+       {
+         *sindex = i;
+         return ((char *)NULL);
+       }
+    }
+
+  result = (flags & SX_NOALLOC) ? (char *)NULL : substring (string, *sindex, i);
+  *sindex = i;
+
+  return (result);
+}
+
+/* Remove backslashes which are quoting backquotes from STRING.  Modifies
+   STRING, and returns a pointer to it. */
+char *
+de_backslash (string)
+     char *string;
+{
+  register size_t slen;
+  register int i, j, prev_i;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  i = j = 0;
+
+  /* Loop copying string[i] to string[j], i >= j. */
+  while (i < slen)
+    {
+      if (string[i] == '\\' && (string[i + 1] == '`' || string[i + 1] == '\\' ||
+                             string[i + 1] == '$'))
+       i++;
+      prev_i = i;
+      ADVANCE_CHAR (string, slen, i);
+      if (j < prev_i)
+       do string[j++] = string[prev_i++]; while (prev_i < i);
+      else
+       j = i;
+    }
+  string[j] = '\0';
+
+  return (string);
+}
+
+#if 0
+/*UNUSED*/
+/* Replace instances of \! in a string with !. */
+void
+unquote_bang (string)
+     char *string;
+{
+  register int i, j;
+  register char *temp;
+
+  temp = (char *)xmalloc (1 + strlen (string));
+
+  for (i = 0, j = 0; (temp[j] = string[i]); i++, j++)
+    {
+      if (string[i] == '\\' && string[i + 1] == '!')
+       {
+         temp[j] = '!';
+         i++;
+       }
+    }
+  strcpy (string, temp);
+  free (temp);
+}
+#endif
+
+#define CQ_RETURN(x) do { no_longjmp_on_fatal_error = 0; return (x); } while (0)
+
+/* This function assumes s[i] == open; returns with s[ret] == close; used to
+   parse array subscripts.  FLAGS & 1 means to not attempt to skip over
+   matched pairs of quotes or backquotes, or skip word expansions; it is
+   intended to be used after expansion has been performed and during final
+   assignment parsing (see arrayfunc.c:assign_compound_array_list()). */
+static int
+skip_matched_pair (string, start, open, close, flags)
+     const char *string;
+     int start, open, close, flags;
+{
+  int i, pass_next, backq, si, c, count;
+  size_t slen;
+  char *temp, *ss;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + start) + start;
+  no_longjmp_on_fatal_error = 1;
+
+  i = start + 1;               /* skip over leading bracket */
+  count = 1;
+  pass_next = backq = 0;
+  ss = (char *)string;
+  while (c = string[i])
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (c == 0)
+           CQ_RETURN(i);
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (backq)
+       {
+         if (c == '`')
+           backq = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if ((flags & 1) == 0 && c == '`')
+       {
+         backq = 1;
+         i++;
+         continue;
+       }
+      else if ((flags & 1) == 0 && c == open)
+       {
+         count++;
+         i++;
+         continue;
+       }
+      else if (c == close)
+       {
+         count--;
+         if (count == 0)
+           break;
+         i++;
+         continue;
+       }
+      else if ((flags & 1) == 0 && (c == '\'' || c == '"'))
+       {
+         i = (c == '\'') ? skip_single_quoted (ss, slen, ++i)
+                         : skip_double_quoted (ss, slen, ++i);
+         /* no increment, the skip functions increment past the closing quote. */
+       }
+      else if ((flags&1) == 0 && c == '$' && (string[i+1] == LPAREN || string[i+1] == LBRACE))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         if (string[i+1] == LPAREN)
+           temp = extract_delimited_string (ss, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+         else
+           temp = extract_dollar_brace_string (ss, &si, 0, SX_NOALLOC);
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+
+  CQ_RETURN(i);
+}
+
+#if defined (ARRAY_VARS)
+int
+skipsubscript (string, start, flags)
+     const char *string;
+     int start, flags;
+{
+  return (skip_matched_pair (string, start, '[', ']', flags));
+}
+#endif
+
+/* Skip characters in STRING until we find a character in DELIMS, and return
+   the index of that character.  START is the index into string at which we
+   begin.  This is similar in spirit to strpbrk, but it returns an index into
+   STRING and takes a starting index.  This little piece of code knows quite
+   a lot of shell syntax.  It's very similar to skip_double_quoted and other
+   functions of that ilk. */
+int
+skip_to_delim (string, start, delims, flags)
+     char *string;
+     int start;
+     char *delims;
+     int flags;
+{
+  int i, pass_next, backq, si, c, invert, skipquote, skipcmd;
+  size_t slen;
+  char *temp, open[3];
+  DECLARE_MBSTATE;
+
+  slen = strlen (string + start) + start;
+  if (flags & SD_NOJMP)
+    no_longjmp_on_fatal_error = 1;
+  invert = (flags & SD_INVERT);
+  skipcmd = (flags & SD_NOSKIPCMD) == 0;
+
+  i = start;
+  pass_next = backq = 0;
+  while (c = string[i])
+    {
+      /* If this is non-zero, we should not let quote characters be delimiters
+        and the current character is a single or double quote.  We should not
+        test whether or not it's a delimiter until after we skip single- or
+        double-quoted strings. */
+      skipquote = ((flags & SD_NOQUOTEDELIM) && (c == '\'' || c =='"'));
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (c == 0)
+           CQ_RETURN(i);
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (backq)
+       {
+         if (c == '`')
+           backq = 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '`')
+       {
+         backq = 1;
+         i++;
+         continue;
+       }
+      else if (skipquote == 0 && invert == 0 && member (c, delims))
+       break;
+      else if (c == '\'' || c == '"')
+       {
+         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+                         : skip_double_quoted (string, slen, ++i);
+         /* no increment, the skip functions increment past the closing quote. */
+       }
+      else if (c == '$' && ((skipcmd && string[i+1] == LPAREN) || string[i+1] == LBRACE))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         if (string[i+1] == LPAREN)
+           temp = extract_delimited_string (string, &si, "$(", "(", ")", SX_NOALLOC|SX_COMMAND); /* ) */
+         else
+           temp = extract_dollar_brace_string (string, &si, 0, SX_NOALLOC);
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+#if defined (PROCESS_SUBSTITUTION)
+      else if (skipcmd && (c == '<' || c == '>') && string[i+1] == LPAREN)
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+         temp = extract_process_subst (string, (c == '<') ? "<(" : ">(", &si);
+         i = si;
+         if (string[i] == '\0')
+           break;
+         i++;
+         continue;
+       }
+#endif /* PROCESS_SUBSTITUTION */
+#if defined (EXTENDED_GLOB)
+      else if ((flags & SD_EXTGLOB) && extended_glob && string[i+1] == LPAREN && member (c, "?*+!@"))
+       {
+         si = i + 2;
+         if (string[si] == '\0')
+           CQ_RETURN(si);
+
+         open[0] = c;
+         open[1] = LPAREN;
+         open[2] = '\0';
+         temp = extract_delimited_string (string, &si, open, "(", ")", SX_NOALLOC); /* ) */
+
+         i = si;
+         if (string[i] == '\0')        /* don't increment i past EOS in loop */
+           break;
+         i++;
+         continue;
+       }
+#endif
+      else if ((skipquote || invert) && (member (c, delims) == 0))
+       break;
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+
+  CQ_RETURN(i);
+}
+
+#if defined (READLINE)
+/* Return 1 if the portion of STRING ending at EINDEX is quoted (there is
+   an unclosed quoted string), or if the character at EINDEX is quoted
+   by a backslash. NO_LONGJMP_ON_FATAL_ERROR is used to flag that the various
+   single and double-quoted string parsing functions should not return an
+   error if there are unclosed quotes or braces.  The characters that this
+   recognizes need to be the same as the contents of
+   rl_completer_quote_characters. */
+
+int
+char_is_quoted (string, eindex)
+     char *string;
+     int eindex;
+{
+  int i, pass_next, c;
+  size_t slen;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  no_longjmp_on_fatal_error = 1;
+  i = pass_next = 0;
+  while (i <= eindex)
+    {
+      c = string[i];
+
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (i >= eindex)      /* XXX was if (i >= eindex - 1) */
+           CQ_RETURN(1);
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (c == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (c == '\'' || c == '"')
+       {
+         i = (c == '\'') ? skip_single_quoted (string, slen, ++i)
+                         : skip_double_quoted (string, slen, ++i);
+         if (i > eindex)
+           CQ_RETURN(1);
+         /* no increment, the skip_xxx functions go one past end */
+       }
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+
+  CQ_RETURN(0);
+}
+
+int
+unclosed_pair (string, eindex, openstr)
+     char *string;
+     int eindex;
+     char *openstr;
+{
+  int i, pass_next, openc, olen;
+  size_t slen;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+  olen = strlen (openstr);
+  i = pass_next = openc = 0;
+  while (i <= eindex)
+    {
+      if (pass_next)
+       {
+         pass_next = 0;
+         if (i >= eindex)      /* XXX was if (i >= eindex - 1) */
+           return 0;
+         ADVANCE_CHAR (string, slen, i);
+         continue;
+       }
+      else if (string[i] == '\\')
+       {
+         pass_next = 1;
+         i++;
+         continue;
+       }
+      else if (STREQN (string + i, openstr, olen))
+       {
+         openc = 1 - openc;
+         i += olen;
+       }
+      else if (string[i] == '\'' || string[i] == '"')
+       {
+         i = (string[i] == '\'') ? skip_single_quoted (string, slen, i)
+                                 : skip_double_quoted (string, slen, i);
+         if (i > eindex)
+           return 0;
+       }
+      else
+       ADVANCE_CHAR (string, slen, i);
+    }
+  return (openc);
+}
+
+/* Split STRING (length SLEN) at DELIMS, and return a WORD_LIST with the
+   individual words.  If DELIMS is NULL, the current value of $IFS is used
+   to split the string, and the function follows the shell field splitting
+   rules.  SENTINEL is an index to look for.  NWP, if non-NULL,
+   gets the number of words in the returned list.  CWP, if non-NULL, gets
+   the index of the word containing SENTINEL.  Non-whitespace chars in
+   DELIMS delimit separate fields. */
+WORD_LIST *
+split_at_delims (string, slen, delims, sentinel, flags, nwp, cwp)
+     char *string;
+     int slen;
+     char *delims;
+     int sentinel, flags;
+     int *nwp, *cwp;
+{
+  int ts, te, i, nw, cw, ifs_split, dflags;
+  char *token, *d, *d2;
+  WORD_LIST *ret, *tl;
+
+  if (string == 0 || *string == '\0')
+    {
+      if (nwp)
+       *nwp = 0;
+      if (cwp)
+       *cwp = 0;       
+      return ((WORD_LIST *)NULL);
+    }
+
+  d = (delims == 0) ? ifs_value : delims;
+  ifs_split = delims == 0;
+
+  /* Make d2 the non-whitespace characters in delims */
+  d2 = 0;
+  if (delims)
+    {
+      size_t slength;
+#if defined (HANDLE_MULTIBYTE)
+      size_t mblength = 1;
+#endif
+      DECLARE_MBSTATE;
+
+      slength = strlen (delims);
+      d2 = (char *)xmalloc (slength + 1);
+      i = ts = 0;
+      while (delims[i])
+       {
+#if defined (HANDLE_MULTIBYTE)
+         mbstate_t state_bak;
+         state_bak = state;
+         mblength = MBRLEN (delims + i, slength, &state);
+         if (MB_INVALIDCH (mblength))
+           state = state_bak;
+         else if (mblength > 1)
+           {
+             memcpy (d2 + ts, delims + i, mblength);
+             ts += mblength;
+             i += mblength;
+             slength -= mblength;
+             continue;
+           }
+#endif
+         if (whitespace (delims[i]) == 0)
+           d2[ts++] = delims[i];
+
+         i++;
+         slength--;
+       }
+      d2[ts] = '\0';
+    }
+
+  ret = (WORD_LIST *)NULL;
+
+  /* Remove sequences of whitespace characters at the start of the string, as
+     long as those characters are delimiters. */
+  for (i = 0; member (string[i], d) && spctabnl (string[i]); i++)
+    ;
+  if (string[i] == '\0')
+    return (ret);
+
+  ts = i;
+  nw = 0;
+  cw = -1;
+  dflags = flags|SD_NOJMP;
+  while (1)
+    {
+      te = skip_to_delim (string, ts, d, dflags);
+
+      /* If we have a non-whitespace delimiter character, use it to make a
+        separate field.  This is just about what $IFS splitting does and
+        is closer to the behavior of the shell parser. */
+      if (ts == te && d2 && member (string[ts], d2))
+       {
+         te = ts + 1;
+         /* If we're using IFS splitting, the non-whitespace delimiter char
+            and any additional IFS whitespace delimits a field. */
+         if (ifs_split)
+           while (member (string[te], d) && spctabnl (string[te]))
+             te++;
+         else
+           while (member (string[te], d2))
+             te++;
+       }
+
+      token = substring (string, ts, te);
+
+      ret = add_string_to_list (token, ret);
+      free (token);
+      nw++;
+
+      if (sentinel >= ts && sentinel <= te)
+       cw = nw;
+
+      /* If the cursor is at whitespace just before word start, set the
+        sentinel word to the current word. */
+      if (cwp && cw == -1 && sentinel == ts-1)
+       cw = nw;
+
+      /* If the cursor is at whitespace between two words, make a new, empty
+        word, add it before (well, after, since the list is in reverse order)
+        the word we just added, and set the current word to that one. */
+      if (cwp && cw == -1 && sentinel < ts)
+       {
+         tl = make_word_list (make_word (""), ret->next);
+         ret->next = tl;
+         cw = nw;
+         nw++;
+       }
+
+      if (string[te] == 0)
+       break;
+
+      i = te;
+      while (member (string[i], d) && (ifs_split || spctabnl(string[i])))
+       i++;
+
+      if (string[i])
+       ts = i;
+      else
+       break;
+    }
+
+  /* Special case for SENTINEL at the end of STRING.  If we haven't found
+     the word containing SENTINEL yet, and the index we're looking for is at
+     the end of STRING (or past the end of the previously-found token,
+     possible if the end of the line is composed solely of IFS whitespace)
+     add an additional null argument and set the current word pointer to that. */
+  if (cwp && cw == -1 && (sentinel >= slen || sentinel >= te))
+    {
+      if (whitespace (string[sentinel - 1]))
+       {
+         token = "";
+         ret = add_string_to_list (token, ret);
+         nw++;
+       }
+      cw = nw;
+    }
+
+  if (nwp)
+    *nwp = nw;
+  if (cwp)
+    *cwp = cw;
+
+  return (REVERSE_LIST (ret, WORD_LIST *));
+}
+#endif /* READLINE */
+
+#if 0
+/* UNUSED */
+/* Extract the name of the variable to bind to from the assignment string. */
+char *
+assignment_name (string)
+     char *string;
+{
+  int offset;
+  char *temp;
+
+  offset = assignment (string, 0);
+  if (offset == 0)
+    return (char *)NULL;
+  temp = substring (string, 0, offset);
+  return (temp);
+}
+#endif
+
+/* **************************************************************** */
+/*                                                                 */
+/*     Functions to convert strings to WORD_LISTs and vice versa    */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Return a single string of all the words in LIST.  SEP is the separator
+   to put between individual elements of LIST in the output string. */
+char *
+string_list_internal (list, sep)
+     WORD_LIST *list;
+     char *sep;
+{
+  register WORD_LIST *t;
+  char *result, *r;
+  int word_len, sep_len, result_size;
+
+  if (list == 0)
+    return ((char *)NULL);
+
+  /* Short-circuit quickly if we don't need to separate anything. */
+  if (list->next == 0)
+    return (savestring (list->word->word));
+
+  /* This is nearly always called with either sep[0] == 0 or sep[1] == 0. */
+  sep_len = STRLEN (sep);
+  result_size = 0;
+
+  for (t = list; t; t = t->next)
+    {
+      if (t != list)
+       result_size += sep_len;
+      result_size += strlen (t->word->word);
+    }
+
+  r = result = (char *)xmalloc (result_size + 1);
+
+  for (t = list; t; t = t->next)
+    {
+      if (t != list && sep_len)
+       {
+         if (sep_len > 1)
+           {
+             FASTCOPY (sep, r, sep_len);
+             r += sep_len;
+           }
+         else
+           *r++ = sep[0];
+       }
+
+      word_len = strlen (t->word->word);
+      FASTCOPY (t->word->word, r, word_len);
+      r += word_len;
+    }
+
+  *r = '\0';
+  return (result);
+}
+
+/* Return a single string of all the words present in LIST, separating
+   each word with a space. */
+char *
+string_list (list)
+     WORD_LIST *list;
+{
+  return (string_list_internal (list, " "));
+}
+
+/* An external interface that can be used by the rest of the shell to
+   obtain a string containing the first character in $IFS.  Handles all
+   the multibyte complications.  If LENP is non-null, it is set to the
+   length of the returned string. */
+char *
+ifs_firstchar (lenp)
+     int *lenp;
+{
+  char *ret;
+  int len;
+
+  ret = xmalloc (MB_LEN_MAX + 1);
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc_len == 1)
+    {
+      ret[0] = ifs_firstc[0];
+      ret[1] = '\0';
+      len = ret[0] ? 1 : 0;
+    }
+  else
+    {
+      memcpy (ret, ifs_firstc, ifs_firstc_len);
+      ret[len = ifs_firstc_len] = '\0';
+    }
+#else
+  ret[0] = ifs_firstc;
+  ret[1] = '\0';
+  len = ret[0] ? 0 : 1;
+#endif
+
+  if (lenp)
+    *lenp = len;
+
+  return ret;
+}
+
+/* Return a single string of all the words present in LIST, obeying the
+   quoting rules for "$*", to wit: (P1003.2, draft 11, 3.5.2) "If the
+   expansion [of $*] appears within a double quoted string, it expands
+   to a single field with the value of each parameter separated by the
+   first character of the IFS variable, or by a <space> if IFS is unset." */
+char *
+string_list_dollar_star (list)
+     WORD_LIST *list;
+{
+  char *ret;
+#if defined (HANDLE_MULTIBYTE)
+#  if defined (__GNUC__)
+  char sep[MB_CUR_MAX + 1];
+#  else
+  char *sep = 0;
+#  endif
+#else
+  char sep[2];
+#endif
+
+#if defined (HANDLE_MULTIBYTE)
+#  if !defined (__GNUC__)
+  sep = (char *)xmalloc (MB_CUR_MAX + 1);
+#  endif /* !__GNUC__ */
+  if (ifs_firstc_len == 1)
+    {
+      sep[0] = ifs_firstc[0];
+      sep[1] = '\0';
+    }
+  else
+    {
+      memcpy (sep, ifs_firstc, ifs_firstc_len);
+      sep[ifs_firstc_len] = '\0';
+    }
+#else
+  sep[0] = ifs_firstc;
+  sep[1] = '\0';
+#endif
+
+  ret = string_list_internal (list, sep);
+#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__)
+  free (sep);
+#endif
+  return ret;
+}
+
+/* Turn $@ into a string.  If (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+   is non-zero, the $@ appears within double quotes, and we should quote
+   the list before converting it into a string.  If IFS is unset, and the
+   word is not quoted, we just need to quote CTLESC and CTLNUL characters
+   in the words in the list, because the default value of $IFS is
+   <space><tab><newline>, IFS characters in the words in the list should
+   also be split.  If IFS is null, and the word is not quoted, we need
+   to quote the words in the list to preserve the positional parameters
+   exactly. */
+char *
+string_list_dollar_at (list, quoted)
+     WORD_LIST *list;
+     int quoted;
+{
+  char *ifs, *ret;
+#if defined (HANDLE_MULTIBYTE)
+#  if defined (__GNUC__)
+  char sep[MB_CUR_MAX + 1];
+#  else
+  char *sep = 0;
+#  endif /* !__GNUC__ */
+#else
+  char sep[2];
+#endif
+  WORD_LIST *tlist;
+
+  /* XXX this could just be ifs = ifs_value; */
+  ifs = ifs_var ? value_cell (ifs_var) : (char *)0;
+
+#if defined (HANDLE_MULTIBYTE)
+#  if !defined (__GNUC__)
+  sep = (char *)xmalloc (MB_CUR_MAX + 1);
+#  endif /* !__GNUC__ */
+  if (ifs && *ifs)
+    {
+      if (ifs_firstc_len == 1)
+       {
+         sep[0] = ifs_firstc[0];
+         sep[1] = '\0';
+       }
+      else
+       {
+         memcpy (sep, ifs_firstc, ifs_firstc_len);
+         sep[ifs_firstc_len] = '\0';
+       }
+    }
+  else
+    {
+      sep[0] = ' ';
+      sep[1] = '\0';
+    }
+#else
+  sep[0] = (ifs == 0 || *ifs == 0) ? ' ' : *ifs;
+  sep[1] = '\0';
+#endif
+
+  /* XXX -- why call quote_list if ifs == 0?  we can get away without doing
+     it now that quote_escapes quotes spaces */
+#if 0
+  tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0))
+#else
+  tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
+#endif
+               ? quote_list (list)
+               : list_quote_escapes (list);
+
+  ret = string_list_internal (tlist, sep);
+#if defined (HANDLE_MULTIBYTE) && !defined (__GNUC__)
+  free (sep);
+#endif
+  return ret;
+}
+
+/* Turn the positional paramters into a string, understanding quoting and
+   the various subtleties of using the first character of $IFS as the
+   separator.  Calls string_list_dollar_at, string_list_dollar_star, and
+   string_list as appropriate. */
+char *
+string_list_pos_params (pchar, list, quoted)
+     int pchar;
+     WORD_LIST *list;
+     int quoted;
+{
+  char *ret;
+  WORD_LIST *tlist;
+
+  if (pchar == '*' && (quoted & Q_DOUBLE_QUOTES))
+    {
+      tlist = quote_list (list);
+      word_list_remove_quoted_nulls (tlist);
+      ret = string_list_dollar_star (tlist);
+    }
+  else if (pchar == '*' && (quoted & Q_HERE_DOCUMENT))
+    {
+      tlist = quote_list (list);
+      word_list_remove_quoted_nulls (tlist);
+      ret = string_list (tlist);
+    }
+  else if (pchar == '*')
+    {
+      /* Even when unquoted, string_list_dollar_star does the right thing
+        making sure that the first character of $IFS is used as the
+        separator. */
+      ret = string_list_dollar_star (list);
+    }
+  else if (pchar == '@' && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+    /* We use string_list_dollar_at, but only if the string is quoted, since
+       that quotes the escapes if it's not, which we don't want.  We could
+       use string_list (the old code did), but that doesn't do the right
+       thing if the first character of $IFS is not a space.  We use
+       string_list_dollar_star if the string is unquoted so we make sure that
+       the elements of $@ are separated by the first character of $IFS for
+       later splitting. */
+    ret = string_list_dollar_at (list, quoted);
+  else if (pchar == '@')
+    ret = string_list_dollar_star (list);
+  else
+    ret = string_list ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? quote_list (list) : list);
+
+  return ret;
+}
+
+/* Return the list of words present in STRING.  Separate the string into
+   words at any of the characters found in SEPARATORS.  If QUOTED is
+   non-zero then word in the list will have its quoted flag set, otherwise
+   the quoted flag is left as make_word () deemed fit.
+
+   This obeys the P1003.2 word splitting semantics.  If `separators' is
+   exactly <space><tab><newline>, then the splitting algorithm is that of
+   the Bourne shell, which treats any sequence of characters from `separators'
+   as a delimiter.  If IFS is unset, which results in `separators' being set
+   to "", no splitting occurs.  If separators has some other value, the
+   following rules are applied (`IFS white space' means zero or more
+   occurrences of <space>, <tab>, or <newline>, as long as those characters
+   are in `separators'):
+
+       1) IFS white space is ignored at the start and the end of the
+          string.
+       2) Each occurrence of a character in `separators' that is not
+          IFS white space, along with any adjacent occurrences of
+          IFS white space delimits a field.
+       3) Any nonzero-length sequence of IFS white space delimits a field.
+   */
+
+/* BEWARE!  list_string strips null arguments.  Don't call it twice and
+   expect to have "" preserved! */
+
+/* This performs word splitting and quoted null character removal on
+   STRING. */
+#define issep(c) \
+       (((separators)[0]) ? ((separators)[1] ? isifs(c) \
+                                             : (c) == (separators)[0]) \
+                          : 0)
+
+WORD_LIST *
+list_string (string, separators, quoted)
+     register char *string, *separators;
+     int quoted;
+{
+  WORD_LIST *result;
+  WORD_DESC *t;
+  char *current_word, *s;
+  int sindex, sh_style_split, whitesep, xflags;
+  size_t slen;
+
+  if (!string || !*string)
+    return ((WORD_LIST *)NULL);
+
+  sh_style_split = separators && separators[0] == ' ' &&
+                                separators[1] == '\t' &&
+                                separators[2] == '\n' &&
+                                separators[3] == '\0';
+  for (xflags = 0, s = ifs_value; s && *s; s++)
+    {
+      if (*s == CTLESC) xflags |= SX_NOCTLESC;
+      else if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+    }
+
+  slen = 0;
+  /* Remove sequences of whitespace at the beginning of STRING, as
+     long as those characters appear in IFS.  Do not do this if
+     STRING is quoted or if there are no separator characters. */
+  if (!quoted || !separators || !*separators)
+    {
+      for (s = string; *s && spctabnl (*s) && issep (*s); s++);
+
+      if (!*s)
+       return ((WORD_LIST *)NULL);
+
+      string = s;
+    }
+
+  /* OK, now STRING points to a word that does not begin with white space.
+     The splitting algorithm is:
+       extract a word, stopping at a separator
+       skip sequences of spc, tab, or nl as long as they are separators
+     This obeys the field splitting rules in Posix.2. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string) : 1;
+  for (result = (WORD_LIST *)NULL, sindex = 0; string[sindex]; )
+    {
+      /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
+        unless multibyte chars are possible. */
+      current_word = string_extract_verbatim (string, slen, &sindex, separators, xflags);
+      if (current_word == 0)
+       break;
+
+      /* If we have a quoted empty string, add a quoted null argument.  We
+        want to preserve the quoted null character iff this is a quoted
+        empty string; otherwise the quoted null characters are removed
+        below. */
+      if (QUOTED_NULL (current_word))
+       {
+         t = alloc_word_desc ();
+         t->word = make_quoted_char ('\0');
+         t->flags |= W_QUOTED|W_HASQUOTEDNULL;
+         result = make_word_list (t, result);
+       }
+      else if (current_word[0] != '\0')
+       {
+         /* If we have something, then add it regardless.  However,
+            perform quoted null character removal on the current word. */
+         remove_quoted_nulls (current_word);
+         result = add_string_to_list (current_word, result);
+         result->word->flags &= ~W_HASQUOTEDNULL;      /* just to be sure */
+         if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+           result->word->flags |= W_QUOTED;
+       }
+
+      /* If we're not doing sequences of separators in the traditional
+        Bourne shell style, then add a quoted null argument. */
+      else if (!sh_style_split && !spctabnl (string[sindex]))
+       {
+         t = alloc_word_desc ();
+         t->word = make_quoted_char ('\0');
+         t->flags |= W_QUOTED|W_HASQUOTEDNULL;
+         result = make_word_list (t, result);
+       }
+
+      free (current_word);
+
+      /* Note whether or not the separator is IFS whitespace, used later. */
+      whitesep = string[sindex] && spctabnl (string[sindex]);
+
+      /* Move past the current separator character. */
+      if (string[sindex])
+       {
+         DECLARE_MBSTATE;
+         ADVANCE_CHAR (string, slen, sindex);
+       }
+
+      /* Now skip sequences of space, tab, or newline characters if they are
+        in the list of separators. */
+      while (string[sindex] && spctabnl (string[sindex]) && issep (string[sindex]))
+       sindex++;
+
+      /* If the first separator was IFS whitespace and the current character
+        is a non-whitespace IFS character, it should be part of the current
+        field delimiter, not a separate delimiter that would result in an
+        empty field.  Look at POSIX.2, 3.6.5, (3)(b). */
+      if (string[sindex] && whitesep && issep (string[sindex]) && !spctabnl (string[sindex]))
+       {
+         sindex++;
+         /* An IFS character that is not IFS white space, along with any
+            adjacent IFS white space, shall delimit a field. (SUSv3) */
+         while (string[sindex] && spctabnl (string[sindex]) && isifs (string[sindex]))
+           sindex++;
+       }
+    }
+  return (REVERSE_LIST (result, WORD_LIST *));
+}
+
+/* Parse a single word from STRING, using SEPARATORS to separate fields.
+   ENDPTR is set to the first character after the word.  This is used by
+   the `read' builtin.  This is never called with SEPARATORS != $IFS;
+   it should be simplified.
+
+   XXX - this function is very similar to list_string; they should be
+        combined - XXX */
+char *
+get_word_from_string (stringp, separators, endptr)
+     char **stringp, *separators, **endptr;
+{
+  register char *s;
+  char *current_word;
+  int sindex, sh_style_split, whitesep, xflags;
+  size_t slen;
+
+  if (!stringp || !*stringp || !**stringp)
+    return ((char *)NULL);
+
+  sh_style_split = separators && separators[0] == ' ' &&
+                                separators[1] == '\t' &&
+                                separators[2] == '\n' &&
+                                separators[3] == '\0';
+  for (xflags = 0, s = ifs_value; s && *s; s++)
+    {
+      if (*s == CTLESC) xflags |= SX_NOCTLESC;
+      if (*s == CTLNUL) xflags |= SX_NOESCCTLNUL;
+    }
+
+  s = *stringp;
+  slen = 0;
+
+  /* Remove sequences of whitespace at the beginning of STRING, as
+     long as those characters appear in IFS. */
+  if (sh_style_split || !separators || !*separators)
+    {
+      for (; *s && spctabnl (*s) && isifs (*s); s++);
+
+      /* If the string is nothing but whitespace, update it and return. */
+      if (!*s)
+       {
+         *stringp = s;
+         if (endptr)
+           *endptr = s;
+         return ((char *)NULL);
+       }
+    }
+
+  /* OK, S points to a word that does not begin with white space.
+     Now extract a word, stopping at a separator, save a pointer to
+     the first character after the word, then skip sequences of spc,
+     tab, or nl as long as they are separators.
+
+     This obeys the field splitting rules in Posix.2. */
+  sindex = 0;
+  /* Don't need string length in ADVANCE_CHAR or string_extract_verbatim
+     unless multibyte chars are possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (s) : 1;
+  current_word = string_extract_verbatim (s, slen, &sindex, separators, xflags);
+
+  /* Set ENDPTR to the first character after the end of the word. */
+  if (endptr)
+    *endptr = s + sindex;
+
+  /* Note whether or not the separator is IFS whitespace, used later. */
+  whitesep = s[sindex] && spctabnl (s[sindex]);
+
+  /* Move past the current separator character. */
+  if (s[sindex])
+    {
+      DECLARE_MBSTATE;
+      ADVANCE_CHAR (s, slen, sindex);
+    }
+
+  /* Now skip sequences of space, tab, or newline characters if they are
+     in the list of separators. */
+  while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
+    sindex++;
+
+  /* If the first separator was IFS whitespace and the current character is
+     a non-whitespace IFS character, it should be part of the current field
+     delimiter, not a separate delimiter that would result in an empty field.
+     Look at POSIX.2, 3.6.5, (3)(b). */
+  if (s[sindex] && whitesep && isifs (s[sindex]) && !spctabnl (s[sindex]))
+    {
+      sindex++;
+      /* An IFS character that is not IFS white space, along with any adjacent
+        IFS white space, shall delimit a field. */
+      while (s[sindex] && spctabnl (s[sindex]) && isifs (s[sindex]))
+       sindex++;
+    }
+
+  /* Update STRING to point to the next field. */
+  *stringp = s + sindex;
+  return (current_word);
+}
+
+/* Remove IFS white space at the end of STRING.  Start at the end
+   of the string and walk backwards until the beginning of the string
+   or we find a character that's not IFS white space and not CTLESC.
+   Only let CTLESC escape a white space character if SAW_ESCAPE is
+   non-zero.  */
+char *
+strip_trailing_ifs_whitespace (string, separators, saw_escape)
+     char *string, *separators;
+     int saw_escape;
+{
+  char *s;
+
+  s = string + STRLEN (string) - 1;
+  while (s > string && ((spctabnl (*s) && isifs (*s)) ||
+                       (saw_escape && *s == CTLESC && spctabnl (s[1]))))
+    s--;
+  *++s = '\0';
+  return string;
+}
+
+#if 0
+/* UNUSED */
+/* Split STRING into words at whitespace.  Obeys shell-style quoting with
+   backslashes, single and double quotes. */
+WORD_LIST *
+list_string_with_quotes (string)
+     char *string;
+{
+  WORD_LIST *list;
+  char *token, *s;
+  size_t s_len;
+  int c, i, tokstart, len;
+
+  for (s = string; s && *s && spctabnl (*s); s++)
+    ;
+  if (s == 0 || *s == 0)
+    return ((WORD_LIST *)NULL);
+
+  s_len = strlen (s);
+  tokstart = i = 0;
+  list = (WORD_LIST *)NULL;
+  while (1)
+    {
+      c = s[i];
+      if (c == '\\')
+       {
+         i++;
+         if (s[i])
+           i++;
+       }
+      else if (c == '\'')
+       i = skip_single_quoted (s, s_len, ++i);
+      else if (c == '"')
+       i = skip_double_quoted (s, s_len, ++i);
+      else if (c == 0 || spctabnl (c))
+       {
+         /* We have found the end of a token.  Make a word out of it and
+            add it to the word list. */
+         token = substring (s, tokstart, i);
+         list = add_string_to_list (token, list);
+         free (token);
+         while (spctabnl (s[i]))
+           i++;
+         if (s[i])
+           tokstart = i;
+         else
+           break;
+       }
+      else
+       i++;    /* normal character */
+    }
+  return (REVERSE_LIST (list, WORD_LIST *));
+}
+#endif
+
+/********************************************************/
+/*                                                     */
+/*     Functions to perform assignment statements      */
+/*                                                     */
+/********************************************************/
+
+#if defined (ARRAY_VARS)
+static SHELL_VAR *
+do_compound_assignment (name, value, flags)
+     char *name, *value;
+     int flags;
+{
+  SHELL_VAR *v;
+  int mklocal, mkassoc;
+  WORD_LIST *list;
+
+  mklocal = flags & ASS_MKLOCAL;
+  mkassoc = flags & ASS_MKASSOC;
+
+  if (mklocal && variable_context)
+    {
+      v = find_variable (name);
+      list = expand_compound_array_assignment (v, value, flags);
+      if (mkassoc)
+       v = make_local_assoc_variable (name);
+      else if (v == 0 || (array_p (v) == 0 && assoc_p (v) == 0) || v->context != variable_context)
+        v = make_local_array_variable (name);
+      assign_compound_array_list (v, list, flags);
+    }
+  else
+    v = assign_array_from_string (name, value, flags);
+
+  return (v);
+}
+#endif
+
+/* Given STRING, an assignment string, get the value of the right side
+   of the `=', and bind it to the left side.  If EXPAND is true, then
+   perform parameter expansion, command substitution, and arithmetic
+   expansion on the right-hand side.  Perform tilde expansion in any
+   case.  Do not perform word splitting on the result of expansion. */
+static int
+do_assignment_internal (word, expand)
+     const WORD_DESC *word;
+     int expand;
+{
+  int offset, appendop, assign_list, aflags, retval;
+  char *name, *value, *temp;
+  SHELL_VAR *entry;
+#if defined (ARRAY_VARS)
+  char *t;
+  int ni;
+#endif
+  const char *string;
+
+  if (word == 0 || word->word == 0)
+    return 0;
+
+  appendop = assign_list = aflags = 0;
+  string = word->word;
+  offset = assignment (string, 0);
+  name = savestring (string);
+  value = (char *)NULL;
+
+  if (name[offset] == '=')
+    {
+      if (name[offset - 1] == '+')
+       {
+         appendop = 1;
+         name[offset - 1] = '\0';
+       }
+
+      name[offset] = 0;                /* might need this set later */
+      temp = name + offset + 1;
+
+#if defined (ARRAY_VARS)
+      if (expand && (word->flags & W_COMPASSIGN))
+       {
+         assign_list = ni = 1;
+         value = extract_array_assignment_list (temp, &ni);
+       }
+      else
+#endif
+      if (expand && temp[0])
+       value = expand_string_if_necessary (temp, 0, expand_string_assignment);
+      else
+       value = savestring (temp);
+    }
+
+  if (value == 0)
+    {
+      value = (char *)xmalloc (1);
+      value[0] = '\0';
+    }
+
+  if (echo_command_at_execute)
+    {
+      if (appendop)
+       name[offset - 1] = '+';
+      xtrace_print_assignment (name, value, assign_list, 1);
+      if (appendop)
+       name[offset - 1] = '\0';
+    }
+
+#define ASSIGN_RETURN(r)       do { FREE (value); free (name); return (r); } while (0)
+
+  if (appendop)
+    aflags |= ASS_APPEND;
+
+#if defined (ARRAY_VARS)
+  if (t = mbschr (name, '['))  /*]*/
+    {
+      if (assign_list)
+       {
+         report_error (_("%s: cannot assign list to array member"), name);
+         ASSIGN_RETURN (0);
+       }
+      entry = assign_array_element (name, value, aflags);
+      if (entry == 0)
+       ASSIGN_RETURN (0);
+    }
+  else if (assign_list)
+    {
+      if (word->flags & W_ASSIGNARG)
+       aflags |= ASS_MKLOCAL;
+      if (word->flags & W_ASSIGNASSOC)
+       aflags |= ASS_MKASSOC;
+      entry = do_compound_assignment (name, value, aflags);
+    }
+  else
+#endif /* ARRAY_VARS */
+  entry = bind_variable (name, value, aflags);
+
+  stupidly_hack_special_variables (name);
+
+#if 1
+  /* Return 1 if the assignment seems to have been performed correctly. */
+  if (entry == 0 || readonly_p (entry))
+    retval = 0;                /* assignment failure */
+  else if (noassign_p (entry))
+    {
+      last_command_exit_value = EXECUTION_FAILURE;
+      retval = 1;      /* error status, but not assignment failure */
+    }
+  else
+    retval = 1;
+
+  if (entry && retval != 0 && noassign_p (entry) == 0)
+    VUNSETATTR (entry, att_invisible);
+
+  ASSIGN_RETURN (retval);
+#else
+  if (entry)
+    VUNSETATTR (entry, att_invisible);
+
+  ASSIGN_RETURN (entry ? ((readonly_p (entry) == 0) && noassign_p (entry) == 0) : 0);
+#endif
+}
+
+/* Perform the assignment statement in STRING, and expand the
+   right side by doing tilde, command and parameter expansion. */
+int
+do_assignment (string)
+     char *string;
+{
+  WORD_DESC td;
+
+  td.flags = W_ASSIGNMENT;
+  td.word = string;
+
+  return do_assignment_internal (&td, 1);
+}
+
+int
+do_word_assignment (word)
+     WORD_DESC *word;
+{
+  return do_assignment_internal (word, 1);
+}
+
+/* Given STRING, an assignment string, get the value of the right side
+   of the `=', and bind it to the left side.  Do not perform any word
+   expansions on the right hand side. */
+int
+do_assignment_no_expand (string)
+     char *string;
+{
+  WORD_DESC td;
+
+  td.flags = W_ASSIGNMENT;
+  td.word = string;
+
+  return (do_assignment_internal (&td, 0));
+}
+
+/***************************************************
+ *                                                *
+ *  Functions to manage the positional parameters  *
+ *                                                *
+ ***************************************************/
+
+/* Return the word list that corresponds to `$*'. */
+WORD_LIST *
+list_rest_of_args ()
+{
+  register WORD_LIST *list, *args;
+  int i;
+
+  /* Break out of the loop as soon as one of the dollar variables is null. */
+  for (i = 1, list = (WORD_LIST *)NULL; i < 10 && dollar_vars[i]; i++)
+    list = make_word_list (make_bare_word (dollar_vars[i]), list);
+
+  for (args = rest_of_args; args; args = args->next)
+    list = make_word_list (make_bare_word (args->word->word), list);
+
+  return (REVERSE_LIST (list, WORD_LIST *));
+}
+
+int
+number_of_args ()
+{
+  register WORD_LIST *list;
+  int n;
+
+  for (n = 0; n < 9 && dollar_vars[n+1]; n++)
+    ;
+  for (list = rest_of_args; list; list = list->next)
+    n++;
+  return n;
+}
+
+/* Return the value of a positional parameter.  This handles values > 10. */
+char *
+get_dollar_var_value (ind)
+     intmax_t ind;
+{
+  char *temp;
+  WORD_LIST *p;
+
+  if (ind < 10)
+    temp = dollar_vars[ind] ? savestring (dollar_vars[ind]) : (char *)NULL;
+  else /* We want something like ${11} */
+    {
+      ind -= 10;
+      for (p = rest_of_args; p && ind--; p = p->next)
+       ;
+      temp = p ? savestring (p->word->word) : (char *)NULL;
+    }
+  return (temp);
+}
+
+/* Make a single large string out of the dollar digit variables,
+   and the rest_of_args.  If DOLLAR_STAR is 1, then obey the special
+   case of "$*" with respect to IFS. */
+char *
+string_rest_of_args (dollar_star)
+     int dollar_star;
+{
+  register WORD_LIST *list;
+  char *string;
+
+  list = list_rest_of_args ();
+  string = dollar_star ? string_list_dollar_star (list) : string_list (list);
+  dispose_words (list);
+  return (string);
+}
+
+/* Return a string containing the positional parameters from START to
+   END, inclusive.  If STRING[0] == '*', we obey the rules for $*,
+   which only makes a difference if QUOTED is non-zero.  If QUOTED includes
+   Q_HERE_DOCUMENT or Q_DOUBLE_QUOTES, this returns a quoted list, otherwise
+   no quoting chars are added. */
+static char *
+pos_params (string, start, end, quoted)
+     char *string;
+     int start, end, quoted;
+{
+  WORD_LIST *save, *params, *h, *t;
+  char *ret;
+  int i;
+
+  /* see if we can short-circuit.  if start == end, we want 0 parameters. */
+  if (start == end)
+    return ((char *)NULL);
+
+  save = params = list_rest_of_args ();
+  if (save == 0)
+    return ((char *)NULL);
+
+  if (start == 0)              /* handle ${@:0[:x]} specially */
+    {
+      t = make_word_list (make_word (dollar_vars[0]), params);
+      save = params = t;
+    }
+
+  for (i = start ? 1 : 0; params && i < start; i++)
+    params = params->next;
+  if (params == 0)
+    return ((char *)NULL);
+  for (h = t = params; params && i < end; i++)
+    {
+      t = params;
+      params = params->next;
+    }
+
+  t->next = (WORD_LIST *)NULL;
+
+  ret = string_list_pos_params (string[0], h, quoted);
+
+  if (t != params)
+    t->next = params;
+
+  dispose_words (save);
+  return (ret);
+}
+
+/******************************************************************/
+/*                                                               */
+/*     Functions to expand strings to strings or WORD_LISTs      */
+/*                                                               */
+/******************************************************************/
+
+#if defined (PROCESS_SUBSTITUTION)
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == '<' || s == '>' || s == CTLESC || s == '~')
+#else
+#define EXP_CHAR(s) (s == '$' || s == '`' || s == CTLESC || s == '~')
+#endif
+
+/* If there are any characters in STRING that require full expansion,
+   then call FUNC to expand STRING; otherwise just perform quote
+   removal if necessary.  This returns a new string. */
+static char *
+expand_string_if_necessary (string, quoted, func)
+     char *string;
+     int quoted;
+     EXPFUNC *func;
+{
+  WORD_LIST *list;
+  size_t slen;
+  int i, saw_quote;
+  char *ret;
+  DECLARE_MBSTATE;
+
+  /* Don't need string length for ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? strlen (string) : 0;
+  i = saw_quote = 0;
+  while (string[i])
+    {
+      if (EXP_CHAR (string[i]))
+       break;
+      else if (string[i] == '\'' || string[i] == '\\' || string[i] == '"')
+       saw_quote = 1;
+      ADVANCE_CHAR (string, slen, i);
+    }
+
+  if (string[i])
+    {
+      list = (*func) (string, quoted);
+      if (list)
+       {
+         ret = string_list (list);
+         dispose_words (list);
+       }
+      else
+       ret = (char *)NULL;
+    }
+  else if (saw_quote && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+    ret = string_quote_removal (string, quoted);
+  else
+    ret = savestring (string);
+
+  return ret;
+}
+
+static inline char *
+expand_string_to_string_internal (string, quoted, func)
+     char *string;
+     int quoted;
+     EXPFUNC *func;
+{
+  WORD_LIST *list;
+  char *ret;
+
+  if (string == 0 || *string == '\0')
+    return ((char *)NULL);
+
+  list = (*func) (string, quoted);
+  if (list)
+    {
+      ret = string_list (list);
+      dispose_words (list);
+    }
+  else
+    ret = (char *)NULL;
+
+  return (ret);
+}
+
+char *
+expand_string_to_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  return (expand_string_to_string_internal (string, quoted, expand_string));
+}
+
+char *
+expand_string_unsplit_to_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  return (expand_string_to_string_internal (string, quoted, expand_string_unsplit));
+}
+
+char *
+expand_assignment_string_to_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  return (expand_string_to_string_internal (string, quoted, expand_string_assignment));
+}
+
+char *
+expand_arith_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  return (expand_string_if_necessary (string, quoted, expand_string));
+}
+
+#if defined (COND_COMMAND)
+/* Just remove backslashes in STRING.  Returns a new string. */
+char *
+remove_backslashes (string)
+     char *string;
+{
+  char *r, *ret, *s;
+
+  r = ret = (char *)xmalloc (strlen (string) + 1);
+  for (s = string; s && *s; )
+    {
+      if (*s == '\\')
+       s++;
+      if (*s == 0)
+       break;
+      *r++ = *s++;
+    }
+  *r = '\0';
+  return ret;
+}
+
+/* This needs better error handling. */
+/* Expand W for use as an argument to a unary or binary operator in a
+   [[...]] expression.  If SPECIAL is 1, this is the rhs argument
+   to the != or == operator, and should be treated as a pattern.  In
+   this case, we quote the string specially for the globbing code.  If
+   SPECIAL is 2, this is an rhs argument for the =~ operator, and should
+   be quoted appropriately for regcomp/regexec.  The caller is responsible
+   for removing the backslashes if the unquoted word is needed later. */   
+char *
+cond_expand_word (w, special)
+     WORD_DESC *w;
+     int special;
+{
+  char *r, *p;
+  WORD_LIST *l;
+  int qflags;
+
+  if (w->word == 0 || w->word[0] == '\0')
+    return ((char *)NULL);
+
+  w->flags |= W_NOSPLIT2;
+  l = call_expand_word_internal (w, 0, 0, (int *)0, (int *)0);
+  if (l)
+    {
+      if (special == 0)
+       {
+         dequote_list (l);
+         r = string_list (l);
+       }
+      else
+       {
+         qflags = QGLOB_CVTNULL;
+         if (special == 2)
+           qflags |= QGLOB_REGEXP;
+         p = string_list (l);
+         r = quote_string_for_globbing (p, qflags);
+         free (p);
+       }
+      dispose_words (l);
+    }
+  else
+    r = (char *)NULL;
+
+  return r;
+}
+#endif
+
+/* Call expand_word_internal to expand W and handle error returns.
+   A convenience function for functions that don't want to handle
+   any errors or free any memory before aborting. */
+static WORD_LIST *
+call_expand_word_internal (w, q, i, c, e)
+     WORD_DESC *w;
+     int q, i, *c, *e;
+{
+  WORD_LIST *result;
+
+  result = expand_word_internal (w, q, i, c, e);
+  if (result == &expand_word_error || result == &expand_word_fatal)
+    {
+      /* By convention, each time this error is returned, w->word has
+        already been freed (it sometimes may not be in the fatal case,
+        but that doesn't result in a memory leak because we're going
+        to exit in most cases). */
+      w->word = (char *)NULL;
+      last_command_exit_value = EXECUTION_FAILURE;
+      exp_jump_to_top_level ((result == &expand_word_error) ? DISCARD : FORCE_EOF);
+      /* NOTREACHED */
+    }
+  else
+    return (result);
+}
+
+/* Perform parameter expansion, command substitution, and arithmetic
+   expansion on STRING, as if it were a word.  Leave the result quoted. */
+static WORD_LIST *
+expand_string_internal (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_DESC td;
+  WORD_LIST *tresult;
+
+  if (string == 0 || *string == 0)
+    return ((WORD_LIST *)NULL);
+
+  td.flags = 0;
+  td.word = savestring (string);
+
+  tresult = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+
+  FREE (td.word);
+  return (tresult);
+}
+
+/* Expand STRING by performing parameter expansion, command substitution,
+   and arithmetic expansion.  Dequote the resulting WORD_LIST before
+   returning it, but do not perform word splitting.  The call to
+   remove_quoted_nulls () is in here because word splitting normally
+   takes care of quote removal. */
+WORD_LIST *
+expand_string_unsplit (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_LIST *value;
+
+  if (string == 0 || *string == '\0')
+    return ((WORD_LIST *)NULL);
+
+  expand_no_split_dollar_star = 1;
+  value = expand_string_internal (string, quoted);
+  expand_no_split_dollar_star = 0;
+
+  if (value)
+    {
+      if (value->word)
+       {
+         remove_quoted_nulls (value->word->word);
+         value->word->flags &= ~W_HASQUOTEDNULL;
+       }
+      dequote_list (value);
+    }
+  return (value);
+}
+
+/* Expand the rhs of an assignment statement */
+WORD_LIST *
+expand_string_assignment (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_DESC td;
+  WORD_LIST *value;
+
+  if (string == 0 || *string == '\0')
+    return ((WORD_LIST *)NULL);
+
+  expand_no_split_dollar_star = 1;
+
+  td.flags = W_ASSIGNRHS;
+  td.word = savestring (string);
+  value = call_expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+  FREE (td.word);
+
+  expand_no_split_dollar_star = 0;
+
+  if (value)
+    {
+      if (value->word)
+       {
+         remove_quoted_nulls (value->word->word);
+         value->word->flags &= ~W_HASQUOTEDNULL;
+       }
+      dequote_list (value);
+    }
+  return (value);
+}
+
+
+/* Expand one of the PS? prompt strings. This is a sort of combination of
+   expand_string_unsplit and expand_string_internal, but returns the
+   passed string when an error occurs.  Might want to trap other calls
+   to jump_to_top_level here so we don't endlessly loop. */
+WORD_LIST *
+expand_prompt_string (string, quoted, wflags)
+     char *string;
+     int quoted;
+     int wflags;
+{
+  WORD_LIST *value;
+  WORD_DESC td;
+
+  if (string == 0 || *string == 0)
+    return ((WORD_LIST *)NULL);
+
+  td.flags = wflags;
+  td.word = savestring (string);
+
+  no_longjmp_on_fatal_error = 1;
+  value = expand_word_internal (&td, quoted, 0, (int *)NULL, (int *)NULL);
+  no_longjmp_on_fatal_error = 0;
+
+  if (value == &expand_word_error || value == &expand_word_fatal)
+    {
+      value = make_word_list (make_bare_word (string), (WORD_LIST *)NULL);
+      return value;
+    }
+  FREE (td.word);
+  if (value)
+    {
+      if (value->word)
+       {
+         remove_quoted_nulls (value->word->word);
+         value->word->flags &= ~W_HASQUOTEDNULL;
+       }
+      dequote_list (value);
+    }
+  return (value);
+}
+
+/* Expand STRING just as if you were expanding a word, but do not dequote
+   the resultant WORD_LIST.  This is called only from within this file,
+   and is used to correctly preserve quoted characters when expanding
+   things like ${1+"$@"}.  This does parameter expansion, command
+   substitution, arithmetic expansion, and word splitting. */
+static WORD_LIST *
+expand_string_leave_quoted (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_LIST *tlist;
+  WORD_LIST *tresult;
+
+  if (string == 0 || *string == '\0')
+    return ((WORD_LIST *)NULL);
+
+  tlist = expand_string_internal (string, quoted);
+
+  if (tlist)
+    {
+      tresult = word_list_split (tlist);
+      dispose_words (tlist);
+      return (tresult);
+    }
+  return ((WORD_LIST *)NULL);
+}
+
+/* This does not perform word splitting or dequote the WORD_LIST
+   it returns. */
+static WORD_LIST *
+expand_string_for_rhs (string, quoted, dollar_at_p, has_dollar_at)
+     char *string;
+     int quoted, *dollar_at_p, *has_dollar_at;
+{
+  WORD_DESC td;
+  WORD_LIST *tresult;
+
+  if (string == 0 || *string == '\0')
+    return (WORD_LIST *)NULL;
+
+  td.flags = 0;
+  td.word = string;
+  tresult = call_expand_word_internal (&td, quoted, 1, dollar_at_p, has_dollar_at);
+  return (tresult);
+}
+
+/* Expand STRING just as if you were expanding a word.  This also returns
+   a list of words.  Note that filename globbing is *NOT* done for word
+   or string expansion, just when the shell is expanding a command.  This
+   does parameter expansion, command substitution, arithmetic expansion,
+   and word splitting.  Dequote the resultant WORD_LIST before returning. */
+WORD_LIST *
+expand_string (string, quoted)
+     char *string;
+     int quoted;
+{
+  WORD_LIST *result;
+
+  if (string == 0 || *string == '\0')
+    return ((WORD_LIST *)NULL);
+
+  result = expand_string_leave_quoted (string, quoted);
+  return (result ? dequote_list (result) : result);
+}
+
+/***************************************************
+ *                                                *
+ *     Functions to handle quoting chars          *
+ *                                                *
+ ***************************************************/
+
+/* Conventions:
+
+     A string with s[0] == CTLNUL && s[1] == 0 is a quoted null string.
+     The parser passes CTLNUL as CTLESC CTLNUL. */
+
+/* Quote escape characters in string s, but no other characters.  This is
+   used to protect CTLESC and CTLNUL in variable values from the rest of
+   the word expansion process after the variable is expanded (word splitting
+   and filename generation).  If IFS is null, we quote spaces as well, just
+   in case we split on spaces later (in the case of unquoted $@, we will
+   eventually attempt to split the entire word on spaces).  Corresponding
+   code exists in dequote_escapes.  Even if we don't end up splitting on
+   spaces, quoting spaces is not a problem.  This should never be called on
+   a string that is quoted with single or double quotes or part of a here
+   document (effectively double-quoted). */
+char *
+quote_escapes (string)
+     char *string;
+{
+  register char *s, *t;
+  size_t slen;
+  char *result, *send;
+  int quote_spaces, skip_ctlesc, skip_ctlnul;
+  DECLARE_MBSTATE; 
+
+  slen = strlen (string);
+  send = string + slen;
+
+  quote_spaces = (ifs_value && *ifs_value == 0);
+
+  for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+    skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
+
+  t = result = (char *)xmalloc ((slen * 2) + 1);
+  s = string;
+
+  while (*s)
+    {
+      if ((skip_ctlesc == 0 && *s == CTLESC) || (skip_ctlnul == 0 && *s == CTLNUL) || (quote_spaces && *s == ' '))
+       *t++ = CTLESC;
+      COPY_CHAR_P (t, s, send);
+    }
+  *t = '\0';
+  return (result);
+}
+
+static WORD_LIST *
+list_quote_escapes (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *w;
+  char *t;
+
+  for (w = list; w; w = w->next)
+    {
+      t = w->word->word;
+      w->word->word = quote_escapes (t);
+      free (t);
+    }
+  return list;
+}
+
+/* Inverse of quote_escapes; remove CTLESC protecting CTLESC or CTLNUL.
+
+   The parser passes us CTLESC as CTLESC CTLESC and CTLNUL as CTLESC CTLNUL.
+   This is necessary to make unquoted CTLESC and CTLNUL characters in the
+   data stream pass through properly.
+
+   We need to remove doubled CTLESC characters inside quoted strings before
+   quoting the entire string, so we do not double the number of CTLESC
+   characters.
+
+   Also used by parts of the pattern substitution code. */
+char *
+dequote_escapes (string)
+     char *string;
+{
+  register char *s, *t, *s1;
+  size_t slen;
+  char *result, *send;
+  int quote_spaces;
+  DECLARE_MBSTATE;
+
+  if (string == 0)
+    return string;
+
+  slen = strlen (string);
+  send = string + slen;
+
+  t = result = (char *)xmalloc (slen + 1);
+
+  if (strchr (string, CTLESC) == 0)
+    return (strcpy (result, string));
+
+  quote_spaces = (ifs_value && *ifs_value == 0);
+
+  s = string;
+  while (*s)
+    {
+      if (*s == CTLESC && (s[1] == CTLESC || s[1] == CTLNUL || (quote_spaces && s[1] == ' ')))
+       {
+         s++;
+         if (*s == '\0')
+           break;
+       }
+      COPY_CHAR_P (t, s, send);
+    }
+  *t = '\0';
+  return result;
+}
+
+/* Return a new string with the quoted representation of character C.
+   This turns "" into QUOTED_NULL, so the W_HASQUOTEDNULL flag needs to be
+   set in any resultant WORD_DESC where this value is the word. */
+static char *
+make_quoted_char (c)
+     int c;
+{
+  char *temp;
+
+  temp = (char *)xmalloc (3);
+  if (c == 0)
+    {
+      temp[0] = CTLNUL;
+      temp[1] = '\0';
+    }
+  else
+    {
+      temp[0] = CTLESC;
+      temp[1] = c;
+      temp[2] = '\0';
+    }
+  return (temp);
+}
+
+/* Quote STRING, returning a new string.  This turns "" into QUOTED_NULL, so
+   the W_HASQUOTEDNULL flag needs to be set in any resultant WORD_DESC where
+   this value is the word. */
+char *
+quote_string (string)
+     char *string;
+{
+  register char *t;
+  size_t slen;
+  char *result, *send;
+
+  if (*string == 0)
+    {
+      result = (char *)xmalloc (2);
+      result[0] = CTLNUL;
+      result[1] = '\0';
+    }
+  else
+    {
+      DECLARE_MBSTATE;
+
+      slen = strlen (string);
+      send = string + slen;
+
+      result = (char *)xmalloc ((slen * 2) + 1);
+
+      for (t = result; string < send; )
+       {
+         *t++ = CTLESC;
+         COPY_CHAR_P (t, string, send);
+       }
+      *t = '\0';
+    }
+  return (result);
+}
+
+/* De-quote quoted characters in STRING. */
+char *
+dequote_string (string)
+     char *string;
+{
+  register char *s, *t;
+  size_t slen;
+  char *result, *send;
+  DECLARE_MBSTATE;
+
+  slen = strlen (string);
+
+  t = result = (char *)xmalloc (slen + 1);
+
+  if (QUOTED_NULL (string))
+    {
+      result[0] = '\0';
+      return (result);
+    }
+
+  /* If no character in the string can be quoted, don't bother examining
+     each character.  Just return a copy of the string passed to us. */
+  if (strchr (string, CTLESC) == NULL)
+    return (strcpy (result, string));
+
+  send = string + slen;
+  s = string;
+  while (*s)
+    {
+      if (*s == CTLESC)
+       {
+         s++;
+         if (*s == '\0')
+           break;
+       }
+      COPY_CHAR_P (t, s, send);
+    }
+
+  *t = '\0';
+  return (result);
+}
+
+/* Quote the entire WORD_LIST list. */
+static WORD_LIST *
+quote_list (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *w;
+  char *t;
+
+  for (w = list; w; w = w->next)
+    {
+      t = w->word->word;
+      w->word->word = quote_string (t);
+      if (*t == 0)
+       w->word->flags |= W_HASQUOTEDNULL;      /* XXX - turn on W_HASQUOTEDNULL here? */
+      w->word->flags |= W_QUOTED;
+      free (t);
+    }
+  return list;
+}
+
+/* De-quote quoted characters in each word in LIST. */
+WORD_LIST *
+dequote_list (list)
+     WORD_LIST *list;
+{
+  register char *s;
+  register WORD_LIST *tlist;
+
+  for (tlist = list; tlist; tlist = tlist->next)
+    {
+      s = dequote_string (tlist->word->word);
+      if (QUOTED_NULL (tlist->word->word))
+       tlist->word->flags &= ~W_HASQUOTEDNULL;
+      free (tlist->word->word);
+      tlist->word->word = s;
+    }
+  return list;
+}
+
+/* Remove CTLESC protecting a CTLESC or CTLNUL in place.  Return the passed
+   string. */
+char *
+remove_quoted_escapes (string)
+     char *string;
+{
+  char *t;
+
+  if (string)
+    {
+      t = dequote_escapes (string);
+      strcpy (string, t);
+      free (t);
+    }
+
+  return (string);
+}
+
+/* Perform quoted null character removal on STRING.  We don't allow any
+   quoted null characters in the middle or at the ends of strings because
+   of how expand_word_internal works.  remove_quoted_nulls () turns
+   STRING into an empty string iff it only consists of a quoted null,
+   and removes all unquoted CTLNUL characters. */
+char *
+remove_quoted_nulls (string)
+     char *string;
+{
+  register size_t slen;
+  register int i, j, prev_i;
+  DECLARE_MBSTATE;
+
+  if (strchr (string, CTLNUL) == 0)            /* XXX */
+    return string;                             /* XXX */
+
+  slen = strlen (string);
+  i = j = 0;
+
+  while (i < slen)
+    {
+      if (string[i] == CTLESC)
+       {
+         /* Old code had j++, but we cannot assume that i == j at this
+            point -- what if a CTLNUL has already been removed from the
+            string?  We don't want to drop the CTLESC or recopy characters
+            that we've already copied down. */
+         i++; string[j++] = CTLESC;
+         if (i == slen)
+           break;
+       }
+      else if (string[i] == CTLNUL)
+       i++;
+
+      prev_i = i;
+      ADVANCE_CHAR (string, slen, i);
+      if (j < prev_i)
+       {
+         do string[j++] = string[prev_i++]; while (prev_i < i);
+       }
+      else
+       j = i;
+    }
+  string[j] = '\0';
+
+  return (string);
+}
+
+/* Perform quoted null character removal on each element of LIST.
+   This modifies LIST. */
+void
+word_list_remove_quoted_nulls (list)
+     WORD_LIST *list;
+{
+  register WORD_LIST *t;
+
+  for (t = list; t; t = t->next)
+    {
+      remove_quoted_nulls (t->word->word);
+      t->word->flags &= ~W_HASQUOTEDNULL;
+    }
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*        Functions for Matching and Removing Patterns             */
+/*                                                                 */
+/* **************************************************************** */
+
+#if defined (HANDLE_MULTIBYTE)
+#if 0 /* Currently unused */
+static unsigned char *
+mb_getcharlens (string, len)
+     char *string;
+     int len;
+{
+  int i, offset, last;
+  unsigned char *ret;
+  char *p;
+  DECLARE_MBSTATE;
+
+  i = offset = 0;
+  last = 0;
+  ret = (unsigned char *)xmalloc (len);
+  memset (ret, 0, len);
+  while (string[last])
+    {
+      ADVANCE_CHAR (string, len, offset);
+      ret[last] = offset - last;
+      last = offset;
+    }
+  return ret;
+}
+#endif
+#endif
+
+/* Remove the portion of PARAM matched by PATTERN according to OP, where OP
+   can have one of 4 values:
+       RP_LONG_LEFT    remove longest matching portion at start of PARAM
+       RP_SHORT_LEFT   remove shortest matching portion at start of PARAM
+       RP_LONG_RIGHT   remove longest matching portion at end of PARAM
+       RP_SHORT_RIGHT  remove shortest matching portion at end of PARAM
+*/
+
+#define RP_LONG_LEFT   1
+#define RP_SHORT_LEFT  2
+#define RP_LONG_RIGHT  3
+#define RP_SHORT_RIGHT 4
+
+/* Returns its first argument if nothing matched; new memory otherwise */
+static char *
+remove_upattern (param, pattern, op)
+     char *param, *pattern;
+     int op;
+{
+  register int len;
+  register char *end;
+  register char *p, *ret, c;
+
+  len = STRLEN (param);
+  end = param + len;
+
+  switch (op)
+    {
+      case RP_LONG_LEFT:       /* remove longest match at start */
+       for (p = end; p >= param; p--)
+         {
+           c = *p; *p = '\0';
+           if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               *p = c;
+               return (savestring (p));
+             }
+           *p = c;
+
+         }
+       break;
+
+      case RP_SHORT_LEFT:      /* remove shortest match at start */
+       for (p = param; p <= end; p++)
+         {
+           c = *p; *p = '\0';
+           if (strmatch (pattern, param, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               *p = c;
+               return (savestring (p));
+             }
+           *p = c;
+         }
+       break;
+
+      case RP_LONG_RIGHT:      /* remove longest match at end */
+       for (p = param; p <= end; p++)
+         {
+           if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               c = *p; *p = '\0';
+               ret = savestring (param);
+               *p = c;
+               return (ret);
+             }
+         }
+       break;
+
+      case RP_SHORT_RIGHT:     /* remove shortest match at end */
+       for (p = end; p >= param; p--)
+         {
+           if (strmatch (pattern, p, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               c = *p; *p = '\0';
+               ret = savestring (param);
+               *p = c;
+               return (ret);
+             }
+         }
+       break;
+    }
+
+  return (param);      /* no match, return original string */
+}
+
+#if defined (HANDLE_MULTIBYTE)
+/* Returns its first argument if nothing matched; new memory otherwise */
+static wchar_t *
+remove_wpattern (wparam, wstrlen, wpattern, op)
+     wchar_t *wparam;
+     size_t wstrlen;
+     wchar_t *wpattern;
+     int op;
+{
+  wchar_t wc, *ret;
+  int n;
+
+  switch (op)
+    {
+      case RP_LONG_LEFT:       /* remove longest match at start */
+        for (n = wstrlen; n >= 0; n--)
+         {
+           wc = wparam[n]; wparam[n] = L'\0';
+           if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               wparam[n] = wc;
+               return (wcsdup (wparam + n));
+             }
+           wparam[n] = wc;
+         }
+       break;
+
+      case RP_SHORT_LEFT:      /* remove shortest match at start */
+       for (n = 0; n <= wstrlen; n++)
+         {
+           wc = wparam[n]; wparam[n] = L'\0';
+           if (wcsmatch (wpattern, wparam, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               wparam[n] = wc;
+               return (wcsdup (wparam + n));
+             }
+           wparam[n] = wc;
+         }
+       break;
+
+      case RP_LONG_RIGHT:      /* remove longest match at end */
+        for (n = 0; n <= wstrlen; n++)
+         {
+           if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               wc = wparam[n]; wparam[n] = L'\0';
+               ret = wcsdup (wparam);
+               wparam[n] = wc;
+               return (ret);
+             }
+         }
+       break;
+
+      case RP_SHORT_RIGHT:     /* remove shortest match at end */
+       for (n = wstrlen; n >= 0; n--)
+         {
+           if (wcsmatch (wpattern, wparam + n, FNMATCH_EXTFLAG) != FNM_NOMATCH)
+             {
+               wc = wparam[n]; wparam[n] = L'\0';
+               ret = wcsdup (wparam);
+               wparam[n] = wc;
+               return (ret);
+             }
+         }
+       break;
+    }
+
+  return (wparam);     /* no match, return original string */
+}
+#endif /* HANDLE_MULTIBYTE */
+
+static char *
+remove_pattern (param, pattern, op)
+     char *param, *pattern;
+     int op;
+{
+  char *xret;
+
+  if (param == NULL)
+    return (param);
+  if (*param == '\0' || pattern == NULL || *pattern == '\0')   /* minor optimization */
+    return (savestring (param));
+
+#if defined (HANDLE_MULTIBYTE)
+  if (MB_CUR_MAX > 1)
+    {
+      wchar_t *ret, *oret;
+      size_t n;
+      wchar_t *wparam, *wpattern;
+      mbstate_t ps;
+
+      n = xdupmbstowcs (&wpattern, NULL, pattern);
+      if (n == (size_t)-1)
+       {
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
+      n = xdupmbstowcs (&wparam, NULL, param);
+      if (n == (size_t)-1)
+       {
+         free (wpattern);
+         xret = remove_upattern (param, pattern, op);
+         return ((xret == param) ? savestring (param) : xret);
+       }
+      oret = ret = remove_wpattern (wparam, n, wpattern, op);
+      /* Don't bother to convert wparam back to multibyte string if nothing
+        matched; just return copy of original string */
+      if (ret == wparam)
+        {
+          free (wparam);
+          free (wpattern);
+          return (savestring (param));
+        }
+
+      free (wparam);
+      free (wpattern);
+
+      n = strlen (param);
+      xret = (char *)xmalloc (n + 1);
+      memset (&ps, '\0', sizeof (mbstate_t));
+      n = wcsrtombs (xret, (const wchar_t **)&ret, n, &ps);
+      xret[n] = '\0';          /* just to make sure */
+      free (oret);
+      return xret;      
+    }
+  else
+#endif
+    {
+      xret = remove_upattern (param, pattern, op);
+      return ((xret == param) ? savestring (param) : xret);
+    }
+}
+
+/* Match PAT anywhere in STRING and return the match boundaries.
+   This returns 1 in case of a successful match, 0 otherwise.  SP
+   and EP are pointers into the string where the match begins and
+   ends, respectively.  MTYPE controls what kind of match is attempted.
+   MATCH_BEG and MATCH_END anchor the match at the beginning and end
+   of the string, respectively.  The longest match is returned. */
+static int
+match_upattern (string, pat, mtype, sp, ep)
+     char *string, *pat;
+     int mtype;
+     char **sp, **ep;
+{
+  int c, len, mlen;
+  register char *p, *p1, *npat;
+  char *end;
+  int n1;
+
+  /* If the pattern doesn't match anywhere in the string, go ahead and
+     short-circuit right away.  A minor optimization, saves a bunch of
+     unnecessary calls to strmatch (up to N calls for a string of N
+     characters) if the match is unsuccessful.  To preserve the semantics
+     of the substring matches below, we make sure that the pattern has
+     `*' as first and last character, making a new pattern if necessary. */
+  /* XXX - check this later if I ever implement `**' with special meaning,
+     since this will potentially result in `**' at the beginning or end */
+  len = STRLEN (pat);
+  if (pat[0] != '*' || (pat[0] == '*' && pat[1] == LPAREN && extended_glob) || pat[len - 1] != '*')
+    {
+      p = npat = (char *)xmalloc (len + 3);
+      p1 = pat;
+      if (*p1 != '*' || (*p1 == '*' && p1[1] == LPAREN && extended_glob))
+       *p++ = '*';
+      while (*p1)
+       *p++ = *p1++;
+      if (p1[-1] != '*' || p[-2] == '\\')
+       *p++ = '*';
+      *p = '\0';
+    }
+  else
+    npat = pat;
+  c = strmatch (npat, string, FNMATCH_EXTFLAG);
+  if (npat != pat)
+    free (npat);
+  if (c == FNM_NOMATCH)
+    return (0);
+
+  len = STRLEN (string);
+  end = string + len;
+
+  mlen = umatchlen (pat, len);
+
+  switch (mtype)
+    {
+    case MATCH_ANY:
+      for (p = string; p <= end; p++)
+       {
+         if (match_pattern_char (pat, p))
+           {
+#if 0
+             for (p1 = end; p1 >= p; p1--)
+#else
+             p1 = (mlen == -1) ? end : p + mlen;
+             /* p1 - p = length of portion of string to be considered
+                p = current position in string
+                mlen = number of characters consumed by match (-1 for entire string)
+                end = end of string
+                we want to break immediately if the potential match len
+                is greater than the number of characters remaining in the
+                string
+             */
+             if (p1 > end)
+               break;
+             for ( ; p1 >= p; p1--)
+#endif
+               {
+                 c = *p1; *p1 = '\0';
+                 if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+                   {
+                     *p1 = c;
+                     *sp = p;
+                     *ep = p1;
+                     return 1;
+                   }
+                 *p1 = c;
+#if 1
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+#endif
+               }
+           }
+       }
+
+      return (0);
+
+    case MATCH_BEG:
+      if (match_pattern_char (pat, string) == 0)
+       return (0);
+
+#if 0
+      for (p = end; p >= string; p--)
+#else
+      for (p = (mlen == -1) ? end : string + mlen; p >= string; p--)
+#endif
+       {
+         c = *p; *p = '\0';
+         if (strmatch (pat, string, FNMATCH_EXTFLAG) == 0)
+           {
+             *p = c;
+             *sp = string;
+             *ep = p;
+             return 1;
+           }
+         *p = c;
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
+       }
+
+      return (0);
+
+    case MATCH_END:
+#if 0
+      for (p = string; p <= end; p++)
+#else
+      for (p = end - ((mlen == -1) ? len : mlen); p <= end; p++)
+#endif
+       {
+         if (strmatch (pat, p, FNMATCH_EXTFLAG) == 0)
+           {
+             *sp = p;
+             *ep = end;
+             return 1;
+           }
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
+       }
+
+      return (0);
+    }
+
+  return (0);
+}
+
+#if defined (HANDLE_MULTIBYTE)
+/* Match WPAT anywhere in WSTRING and return the match boundaries.
+   This returns 1 in case of a successful match, 0 otherwise.  Wide
+   character version. */
+static int
+match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
+     wchar_t *wstring;
+     char **indices;
+     size_t wstrlen;
+     wchar_t *wpat;
+     int mtype;
+     char **sp, **ep;
+{
+  wchar_t wc, *wp, *nwpat, *wp1;
+  size_t len;
+  int mlen;
+  int n, n1;
+
+  /* If the pattern doesn't match anywhere in the string, go ahead and
+     short-circuit right away.  A minor optimization, saves a bunch of
+     unnecessary calls to strmatch (up to N calls for a string of N
+     characters) if the match is unsuccessful.  To preserve the semantics
+     of the substring matches below, we make sure that the pattern has
+     `*' as first and last character, making a new pattern if necessary. */
+  len = wcslen (wpat);
+  if (wpat[0] != L'*' || (wpat[0] == L'*' && wpat[1] == WLPAREN && extended_glob) || wpat[len - 1] != L'*')
+    {
+      wp = nwpat = (wchar_t *)xmalloc ((len + 3) * sizeof (wchar_t));
+      wp1 = wpat;
+      if (*wp1 != L'*' || (*wp1 == '*' && wp1[1] == WLPAREN && extended_glob))
+       *wp++ = L'*';
+      while (*wp1 != L'\0')
+       *wp++ = *wp1++;
+      if (wp1[-1] != L'*' || wp1[-2] == L'\\')
+        *wp++ = L'*';
+      *wp = '\0';
+    }
+  else
+    nwpat = wpat;
+  len = wcsmatch (nwpat, wstring, FNMATCH_EXTFLAG);
+  if (nwpat != wpat)
+    free (nwpat);
+  if (len == FNM_NOMATCH)
+    return (0);
+
+  mlen = wmatchlen (wpat, wstrlen);
+/* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */
+  switch (mtype)
+    {
+    case MATCH_ANY:
+      for (n = 0; n <= wstrlen; n++)
+       {
+         if (match_pattern_wchar (wpat, wstring + n))
+           {
+#if 0
+             for (n1 = wstrlen; n1 >= n; n1--)
+#else
+             n1 = (mlen == -1) ? wstrlen : n + mlen;
+             if (n1 > wstrlen)
+               break;
+
+             for ( ; n1 >= n; n1--)
+#endif
+               {
+                 wc = wstring[n1]; wstring[n1] = L'\0';
+                 if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+                   {
+                     wstring[n1] = wc;
+                     *sp = indices[n];
+                     *ep = indices[n1];
+                     return 1;
+                   }
+                 wstring[n1] = wc;
+#if 1
+                 /* If MLEN != -1, we have a fixed length pattern. */
+                 if (mlen != -1)
+                   break;
+#endif
+               }
+           }
+       }
+
+      return (0);
+
+    case MATCH_BEG:
+      if (match_pattern_wchar (wpat, wstring) == 0)
+       return (0);
+
+#if 0
+      for (n = wstrlen; n >= 0; n--)
+#else
+      for (n = (mlen == -1) ? wstrlen : mlen; n >= 0; n--)
+#endif
+       {
+         wc = wstring[n]; wstring[n] = L'\0';
+         if (wcsmatch (wpat, wstring, FNMATCH_EXTFLAG) == 0)
+           {
+             wstring[n] = wc;
+             *sp = indices[0];
+             *ep = indices[n];
+             return 1;
+           }
+         wstring[n] = wc;
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
+       }
+
+      return (0);
+
+    case MATCH_END:
+#if 0
+      for (n = 0; n <= wstrlen; n++)
+#else
+      for (n = wstrlen - ((mlen == -1) ? wstrlen : mlen); n <= wstrlen; n++)
+#endif
+       {
+         if (wcsmatch (wpat, wstring + n, FNMATCH_EXTFLAG) == 0)
+           {
+             *sp = indices[n];
+             *ep = indices[wstrlen];
+             return 1;
+           }
+#if 1
+         /* If MLEN != -1, we have a fixed length pattern. */
+         if (mlen != -1)
+           break;
+#endif
+       }
+
+      return (0);
+    }
+
+  return (0);
+}
+#endif /* HANDLE_MULTIBYTE */
+
+static int
+match_pattern (string, pat, mtype, sp, ep)
+     char *string, *pat;
+     int mtype;
+     char **sp, **ep;
+{
+#if defined (HANDLE_MULTIBYTE)
+  int ret;
+  size_t n;
+  wchar_t *wstring, *wpat;
+  char **indices;
+#endif
+
+  if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
+    return (0);
+
+#if defined (HANDLE_MULTIBYTE)
+  if (MB_CUR_MAX > 1)
+    {
+      n = xdupmbstowcs (&wpat, NULL, pat);
+      if (n == (size_t)-1)
+       return (match_upattern (string, pat, mtype, sp, ep));
+      n = xdupmbstowcs (&wstring, &indices, string);
+      if (n == (size_t)-1)
+       {
+         free (wpat);
+         return (match_upattern (string, pat, mtype, sp, ep));
+       }
+      ret = match_wpattern (wstring, indices, n, wpat, mtype, sp, ep);
+
+      free (wpat);
+      free (wstring);
+      free (indices);
+
+      return (ret);
+    }
+  else
+#endif
+    return (match_upattern (string, pat, mtype, sp, ep));
+}
+
+static int
+getpatspec (c, value)
+     int c;
+     char *value;
+{
+  if (c == '#')
+    return ((*value == '#') ? RP_LONG_LEFT : RP_SHORT_LEFT);
+  else /* c == '%' */
+    return ((*value == '%') ? RP_LONG_RIGHT : RP_SHORT_RIGHT);
+}
+
+/* Posix.2 says that the WORD should be run through tilde expansion,
+   parameter expansion, command substitution and arithmetic expansion.
+   This leaves the result quoted, so quote_string_for_globbing () has
+   to be called to fix it up for strmatch ().  If QUOTED is non-zero,
+   it means that the entire expression was enclosed in double quotes.
+   This means that quoting characters in the pattern do not make any
+   special pattern characters quoted.  For example, the `*' in the
+   following retains its special meaning: "${foo#'*'}". */
+static char *
+getpattern (value, quoted, expandpat)
+     char *value;
+     int quoted, expandpat;
+{
+  char *pat, *tword;
+  WORD_LIST *l;
+#if 0
+  int i;
+#endif
+  /* There is a problem here:  how to handle single or double quotes in the
+     pattern string when the whole expression is between double quotes?
+     POSIX.2 says that enclosing double quotes do not cause the pattern to
+     be quoted, but does that leave us a problem with @ and array[@] and their
+     expansions inside a pattern? */
+#if 0
+  if (expandpat && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *tword)
+    {
+      i = 0;
+      pat = string_extract_double_quoted (tword, &i, 1);
+      free (tword);
+      tword = pat;
+    }
+#endif
+
+  /* expand_string_for_rhs () leaves WORD quoted and does not perform
+     word splitting. */
+  l = *value ? expand_string_for_rhs (value,
+                                     (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) ? Q_PATQUOTE : quoted,
+                                     (int *)NULL, (int *)NULL)
+            : (WORD_LIST *)0;
+  pat = string_list (l);
+  dispose_words (l);
+  if (pat)
+    {
+      tword = quote_string_for_globbing (pat, QGLOB_CVTNULL);
+      free (pat);
+      pat = tword;
+    }
+  return (pat);
+}
+
+#if 0
+/* Handle removing a pattern from a string as a result of ${name%[%]value}
+   or ${name#[#]value}. */
+static char *
+variable_remove_pattern (value, pattern, patspec, quoted)
+     char *value, *pattern;
+     int patspec, quoted;
+{
+  char *tword;
+
+  tword = remove_pattern (value, pattern, patspec);
+
+  return (tword);
+}
+#endif
+
+static char *
+list_remove_pattern (list, pattern, patspec, itype, quoted)
+     WORD_LIST *list;
+     char *pattern;
+     int patspec, itype, quoted;
+{
+  WORD_LIST *new, *l;
+  WORD_DESC *w;
+  char *tword;
+
+  for (new = (WORD_LIST *)NULL, l = list; l; l = l->next)
+    {
+      tword = remove_pattern (l->word->word, pattern, patspec);
+      w = alloc_word_desc ();
+      w->word = tword ? tword : savestring ("");
+      new = make_word_list (w, new);
+    }
+
+  l = REVERSE_LIST (new, WORD_LIST *);
+  tword = string_list_pos_params (itype, l, quoted);
+  dispose_words (l);
+
+  return (tword);
+}
+
+static char *
+parameter_list_remove_pattern (itype, pattern, patspec, quoted)
+     int itype;
+     char *pattern;
+     int patspec, quoted;
+{
+  char *ret;
+  WORD_LIST *list;
+
+  list = list_rest_of_args ();
+  if (list == 0)
+    return ((char *)NULL);
+  ret = list_remove_pattern (list, pattern, patspec, itype, quoted);
+  dispose_words (list);
+  return (ret);
+}
+
+#if defined (ARRAY_VARS)
+static char *
+array_remove_pattern (var, pattern, patspec, varname, quoted)
+     SHELL_VAR *var;
+     char *pattern;
+     int patspec;
+     char *varname;    /* so we can figure out how it's indexed */
+     int quoted;
+{
+  ARRAY *a;
+  HASH_TABLE *h;
+  int itype;
+  char *ret;
+  WORD_LIST *list;
+  SHELL_VAR *v;
+
+  /* compute itype from varname here */
+  v = array_variable_part (varname, &ret, 0);
+  itype = ret[0];
+
+  a = (v && array_p (v)) ? array_cell (v) : 0;
+  h = (v && assoc_p (v)) ? assoc_cell (v) : 0;
+  
+  list = a ? array_to_word_list (a) : (h ? assoc_to_word_list (h) : 0);
+  if (list == 0)
+   return ((char *)NULL);
+  ret = list_remove_pattern (list, pattern, patspec, itype, quoted);
+  dispose_words (list);
+
+  return ret;
+}
+#endif /* ARRAY_VARS */
+
+static char *
+parameter_brace_remove_pattern (varname, value, ind, patstr, rtype, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patstr;
+     int rtype, quoted, flags;
+{
+  int vtype, patspec, starsub;
+  char *temp1, *val, *pattern;
+  SHELL_VAR *v;
+
+  if (value == 0)
+    return ((char *)NULL);
+
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    return ((char *)NULL);
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  patspec = getpatspec (rtype, patstr);
+  if (patspec == RP_LONG_LEFT || patspec == RP_LONG_RIGHT)
+    patstr++;
+
+  /* Need to pass getpattern newly-allocated memory in case of expansion --
+     the expansion code will free the passed string on an error. */
+  temp1 = savestring (patstr);
+  pattern = getpattern (temp1, quoted, 1);
+  free (temp1);
+
+  temp1 = (char *)NULL;                /* shut up gcc */
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      temp1 = remove_pattern (val, pattern, patspec);
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (temp1)
+       {
+         val = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+                       ? quote_string (temp1)
+                       : quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      temp1 = array_remove_pattern (v, pattern, patspec, varname, quoted);
+      if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+       {
+         val = quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+#endif
+    case VT_POSPARMS:
+      temp1 = parameter_list_remove_pattern (varname[0], pattern, patspec, quoted);
+      if (temp1 && ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) == 0))
+       {
+         val = quote_escapes (temp1);
+         free (temp1);
+         temp1 = val;
+       }
+      break;
+    }
+
+  FREE (pattern);
+  return temp1;
+}    
+
+/*******************************************
+ *                                        *
+ *     Functions to expand WORD_DESCs     *
+ *                                        *
+ *******************************************/
+
+/* Expand WORD, performing word splitting on the result.  This does
+   parameter expansion, command substitution, arithmetic expansion,
+   word splitting, and quote removal. */
+
+WORD_LIST *
+expand_word (word, quoted)
+     WORD_DESC *word;
+     int quoted;
+{
+  WORD_LIST *result, *tresult;
+
+  tresult = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+  result = word_list_split (tresult);
+  dispose_words (tresult);
+  return (result ? dequote_list (result) : result);
+}
+
+/* Expand WORD, but do not perform word splitting on the result.  This
+   does parameter expansion, command substitution, arithmetic expansion,
+   and quote removal. */
+WORD_LIST *
+expand_word_unsplit (word, quoted)
+     WORD_DESC *word;
+     int quoted;
+{
+  WORD_LIST *result;
+
+  expand_no_split_dollar_star = 1;
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc[0] == 0)
+#else
+  if (ifs_firstc == 0)
+#endif
+    word->flags |= W_NOSPLIT;
+  result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+  expand_no_split_dollar_star = 0;
+
+  return (result ? dequote_list (result) : result);
+}
+
+/* Perform shell expansions on WORD, but do not perform word splitting or
+   quote removal on the result.  Virtually identical to expand_word_unsplit;
+   could be combined if implementations don't diverge. */
+WORD_LIST *
+expand_word_leave_quoted (word, quoted)
+     WORD_DESC *word;
+     int quoted;
+{
+  WORD_LIST *result;
+
+  expand_no_split_dollar_star = 1;
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_firstc[0] == 0)
+#else
+  if (ifs_firstc == 0)
+#endif
+    word->flags |= W_NOSPLIT;
+  word->flags |= W_NOSPLIT2;
+  result = call_expand_word_internal (word, quoted, 0, (int *)NULL, (int *)NULL);
+  expand_no_split_dollar_star = 0;
+
+  return result;
+}
+
+#if defined (PROCESS_SUBSTITUTION)
+
+/*****************************************************************/
+/*                                                              */
+/*                 Hacking Process Substitution                 */
+/*                                                              */
+/*****************************************************************/
+
+#if !defined (HAVE_DEV_FD)
+/* Named pipes must be removed explicitly with `unlink'.  This keeps a list
+   of FIFOs the shell has open.  unlink_fifo_list will walk the list and
+   unlink all of them. add_fifo_list adds the name of an open FIFO to the
+   list.  NFIFO is a count of the number of FIFOs in the list. */
+#define FIFO_INCR 20
+
+struct temp_fifo {
+  char *file;
+  pid_t proc;
+};
+
+static struct temp_fifo *fifo_list = (struct temp_fifo *)NULL;
+static int nfifo;
+static int fifo_list_size;
+
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  if (sizep)
+    *sizep = 0;
+  return (char *)NULL;
+}
+
+static void
+add_fifo_list (pathname)
+     char *pathname;
+{
+  if (nfifo >= fifo_list_size - 1)
+    {
+      fifo_list_size += FIFO_INCR;
+      fifo_list = (struct temp_fifo *)xrealloc (fifo_list,
+                               fifo_list_size * sizeof (struct temp_fifo));
+    }
+
+  fifo_list[nfifo].file = savestring (pathname);
+  nfifo++;
+}
+
+void
+unlink_fifo (i)
+     int i;
+{
+  if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1))
+    {
+      unlink (fifo_list[i].file);
+      free (fifo_list[i].file);
+      fifo_list[i].file = (char *)NULL;
+      fifo_list[i].proc = -1;
+    }
+}
+
+void
+unlink_fifo_list ()
+{
+  int saved, i, j;
+
+  if (nfifo == 0)
+    return;
+
+  for (i = saved = 0; i < nfifo; i++)
+    {
+      if ((fifo_list[i].proc == -1) || (kill(fifo_list[i].proc, 0) == -1))
+       {
+         unlink (fifo_list[i].file);
+         free (fifo_list[i].file);
+         fifo_list[i].file = (char *)NULL;
+         fifo_list[i].proc = -1;
+       }
+      else
+       saved++;
+    }
+
+  /* If we didn't remove some of the FIFOs, compact the list. */
+  if (saved)
+    {
+      for (i = j = 0; i < nfifo; i++)
+       if (fifo_list[i].file)
+         {
+           fifo_list[j].file = fifo_list[i].file;
+           fifo_list[j].proc = fifo_list[i].proc;
+           j++;
+         }
+      nfifo = j;
+    }
+  else
+    nfifo = 0;
+}
+
+/* Take LIST, which is a bitmap denoting active FIFOs in fifo_list
+   from some point in the past, and close all open FIFOs in fifo_list
+   that are not marked as active in LIST.  If LIST is NULL, close
+   everything in fifo_list. LSIZE is the number of elements in LIST, in
+   case it's larger than fifo_list_size (size of fifo_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < fifo_list_size && fifo_list[i].proc != -1)
+      unlink_fifo (i);
+
+  for (i = lsize; i < fifo_list_size; i++)
+    unlink_fifo (i);  
+}
+
+int
+fifos_pending ()
+{
+  return nfifo;
+}
+
+int
+num_fifos ()
+{
+  return nfifo;
+}
+
+static char *
+make_named_pipe ()
+{
+  char *tname;
+
+  tname = sh_mktmpname ("sh-np", MT_USERANDOM|MT_USETMPDIR);
+  if (mkfifo (tname, 0600) < 0)
+    {
+      free (tname);
+      return ((char *)NULL);
+    }
+
+  add_fifo_list (tname);
+  return (tname);
+}
+
+#else /* HAVE_DEV_FD */
+
+/* DEV_FD_LIST is a bitmap of file descriptors attached to pipes the shell
+   has open to children.  NFDS is a count of the number of bits currently
+   set in DEV_FD_LIST.  TOTFDS is a count of the highest possible number
+   of open files. */
+static char *dev_fd_list = (char *)NULL;
+static int nfds;
+static int totfds;     /* The highest possible number of open files. */
+
+char *
+copy_fifo_list (sizep)
+     int *sizep;
+{
+  char *ret;
+
+  if (nfds == 0 || totfds == 0)
+    {
+      if (sizep)
+       *sizep = 0;
+      return (char *)NULL;
+    }
+
+  if (sizep)
+    *sizep = totfds;
+  ret = (char *)xmalloc (totfds);
+  return (memcpy (ret, dev_fd_list, totfds));
+}
+
+static void
+add_fifo_list (fd)
+     int fd;
+{
+  if (dev_fd_list == 0 || fd >= totfds)
+    {
+      int ofds;
+
+      ofds = totfds;
+      totfds = getdtablesize ();
+      if (totfds < 0 || totfds > 256)
+       totfds = 256;
+      if (fd >= totfds)
+       totfds = fd + 2;
+
+      dev_fd_list = (char *)xrealloc (dev_fd_list, totfds);
+      memset (dev_fd_list + ofds, '\0', totfds - ofds);
+    }
+
+  dev_fd_list[fd] = 1;
+  nfds++;
+}
+
+int
+fifos_pending ()
+{
+  return 0;    /* used for cleanup; not needed with /dev/fd */
+}
+
+int
+num_fifos ()
+{
+  return nfds;
+}
+
+void
+unlink_fifo (fd)
+     int fd;
+{
+  if (dev_fd_list[fd])
+    {
+      close (fd);
+      dev_fd_list[fd] = 0;
+      nfds--;
+    }
+}
+
+void
+unlink_fifo_list ()
+{
+  register int i;
+
+  if (nfds == 0)
+    return;
+
+  for (i = 0; nfds && i < totfds; i++)
+    unlink_fifo (i);
+
+  nfds = 0;
+}
+
+/* Take LIST, which is a snapshot copy of dev_fd_list from some point in
+   the past, and close all open fds in dev_fd_list that are not marked
+   as open in LIST.  If LIST is NULL, close everything in dev_fd_list.
+   LSIZE is the number of elements in LIST, in case it's larger than
+   totfds (size of dev_fd_list). */
+void
+close_new_fifos (list, lsize)
+     char *list;
+     int lsize;
+{
+  int i;
+
+  if (list == 0)
+    {
+      unlink_fifo_list ();
+      return;
+    }
+
+  for (i = 0; i < lsize; i++)
+    if (list[i] == 0 && i < totfds && dev_fd_list[i])
+      unlink_fifo (i);
+
+  for (i = lsize; i < totfds; i++)
+    unlink_fifo (i);  
+}
+
+#if defined (NOTDEF)
+print_dev_fd_list ()
+{
+  register int i;
+
+  fprintf (stderr, "pid %ld: dev_fd_list:", (long)getpid ());
+  fflush (stderr);
+
+  for (i = 0; i < totfds; i++)
+    {
+      if (dev_fd_list[i])
+       fprintf (stderr, " %d", i);
+    }
+  fprintf (stderr, "\n");
+}
+#endif /* NOTDEF */
+
+static char *
+make_dev_fd_filename (fd)
+     int fd;
+{
+  char *ret, intbuf[INT_STRLEN_BOUND (int) + 1], *p;
+
+  ret = (char *)xmalloc (sizeof (DEV_FD_PREFIX) + 8);
+
+  strcpy (ret, DEV_FD_PREFIX);
+  p = inttostr (fd, intbuf, sizeof (intbuf));
+  strcpy (ret + sizeof (DEV_FD_PREFIX) - 1, p);
+
+  add_fifo_list (fd);
+  return (ret);
+}
+
+#endif /* HAVE_DEV_FD */
+
+/* Return a filename that will open a connection to the process defined by
+   executing STRING.  HAVE_DEV_FD, if defined, means open a pipe and return
+   a filename in /dev/fd corresponding to a descriptor that is one of the
+   ends of the pipe.  If not defined, we use named pipes on systems that have
+   them.  Systems without /dev/fd and named pipes are out of luck.
+
+   OPEN_FOR_READ_IN_CHILD, if 1, means open the named pipe for reading or
+   use the read end of the pipe and dup that file descriptor to fd 0 in
+   the child.  If OPEN_FOR_READ_IN_CHILD is 0, we open the named pipe for
+   writing or use the write end of the pipe in the child, and dup that
+   file descriptor to fd 1 in the child.  The parent does the opposite. */
+
+static char *
+process_substitute (string, open_for_read_in_child)
+     char *string;
+     int open_for_read_in_child;
+{
+  char *pathname;
+  int fd, result;
+  pid_t old_pid, pid;
+#if defined (HAVE_DEV_FD)
+  int parent_pipe_fd, child_pipe_fd;
+  int fildes[2];
+#endif /* HAVE_DEV_FD */
+#if defined (JOB_CONTROL)
+  pid_t old_pipeline_pgrp;
+#endif
+
+  if (!string || !*string || wordexp_only)
+    return ((char *)NULL);
+
+#if !defined (HAVE_DEV_FD)
+  pathname = make_named_pipe ();
+#else /* HAVE_DEV_FD */
+  if (pipe (fildes) < 0)
+    {
+      sys_error (_("cannot make pipe for process substitution"));
+      return ((char *)NULL);
+    }
+  /* If OPEN_FOR_READ_IN_CHILD == 1, we want to use the write end of
+     the pipe in the parent, otherwise the read end. */
+  parent_pipe_fd = fildes[open_for_read_in_child];
+  child_pipe_fd = fildes[1 - open_for_read_in_child];
+  /* Move the parent end of the pipe to some high file descriptor, to
+     avoid clashes with FDs used by the script. */
+  parent_pipe_fd = move_to_high_fd (parent_pipe_fd, 1, 64);
+
+  pathname = make_dev_fd_filename (parent_pipe_fd);
+#endif /* HAVE_DEV_FD */
+
+  if (pathname == 0)
+    {
+      sys_error (_("cannot make pipe for process substitution"));
+      return ((char *)NULL);
+    }
+
+  old_pid = last_made_pid;
+
+#if defined (JOB_CONTROL)
+  old_pipeline_pgrp = pipeline_pgrp;
+  pipeline_pgrp = shell_pgrp;
+  save_pipeline (1);
+#endif /* JOB_CONTROL */
+
+  pid = make_child ((char *)NULL, 1);
+  if (pid == 0)
+    {
+      reset_terminating_signals ();    /* XXX */
+      free_pushed_string_input ();
+      /* Cancel traps, in trap.c. */
+      restore_original_signals ();     /* XXX - what about special builtins? bash-4.2 */
+      setup_async_signals ();
+      subshell_environment |= SUBSHELL_COMSUB|SUBSHELL_PROCSUB;
+    }
+
+#if defined (JOB_CONTROL)
+  set_sigchld_handler ();
+  stop_making_children ();
+  /* XXX - should we only do this in the parent? (as in command subst) */
+  pipeline_pgrp = old_pipeline_pgrp;
+#endif /* JOB_CONTROL */
+
+  if (pid < 0)
+    {
+      sys_error (_("cannot make child for process substitution"));
+      free (pathname);
+#if defined (HAVE_DEV_FD)
+      close (parent_pipe_fd);
+      close (child_pipe_fd);
+#endif /* HAVE_DEV_FD */
+      return ((char *)NULL);
+    }
+
+  if (pid > 0)
+    {
+#if defined (JOB_CONTROL)
+      restore_pipeline (1);
+#endif
+
+#if !defined (HAVE_DEV_FD)
+      fifo_list[nfifo-1].proc = pid;
+#endif
+
+      last_made_pid = old_pid;
+
+#if defined (JOB_CONTROL) && defined (PGRP_PIPE)
+      close_pgrp_pipe ();
+#endif /* JOB_CONTROL && PGRP_PIPE */
+
+#if defined (HAVE_DEV_FD)
+      close (child_pipe_fd);
+#endif /* HAVE_DEV_FD */
+
+      return (pathname);
+    }
+
+  set_sigint_handler ();
+
+#if defined (JOB_CONTROL)
+  set_job_control (0);
+#endif /* JOB_CONTROL */
+
+#if !defined (HAVE_DEV_FD)
+  /* Open the named pipe in the child. */
+  fd = open (pathname, open_for_read_in_child ? O_RDONLY|O_NONBLOCK : O_WRONLY);
+  if (fd < 0)
+    {
+      /* Two separate strings for ease of translation. */
+      if (open_for_read_in_child)
+       sys_error (_("cannot open named pipe %s for reading"), pathname);
+      else
+       sys_error (_("cannot open named pipe %s for writing"), pathname);
+
+      exit (127);
+    }
+  if (open_for_read_in_child)
+    {
+      if (sh_unset_nodelay_mode (fd) < 0)
+       {
+         sys_error (_("cannot reset nodelay mode for fd %d"), fd);
+         exit (127);
+       }
+    }
+#else /* HAVE_DEV_FD */
+  fd = child_pipe_fd;
+#endif /* HAVE_DEV_FD */
+
+  if (dup2 (fd, open_for_read_in_child ? 0 : 1) < 0)
+    {
+      sys_error (_("cannot duplicate named pipe %s as fd %d"), pathname,
+       open_for_read_in_child ? 0 : 1);
+      exit (127);
+    }
+
+  if (fd != (open_for_read_in_child ? 0 : 1))
+    close (fd);
+
+  /* Need to close any files that this process has open to pipes inherited
+     from its parent. */
+  if (current_fds_to_close)
+    {
+      close_fd_bitmap (current_fds_to_close);
+      current_fds_to_close = (struct fd_bitmap *)NULL;
+    }
+
+#if defined (HAVE_DEV_FD)
+  /* Make sure we close the parent's end of the pipe and clear the slot
+     in the fd list so it is not closed later, if reallocated by, for
+     instance, pipe(2). */
+  close (parent_pipe_fd);
+  dev_fd_list[parent_pipe_fd] = 0;
+#endif /* HAVE_DEV_FD */
+
+  result = parse_and_execute (string, "process substitution", (SEVAL_NONINT|SEVAL_NOHIST));
+
+#if !defined (HAVE_DEV_FD)
+  /* Make sure we close the named pipe in the child before we exit. */
+  close (open_for_read_in_child ? 0 : 1);
+#endif /* !HAVE_DEV_FD */
+
+  exit (result);
+  /*NOTREACHED*/
+}
+#endif /* PROCESS_SUBSTITUTION */
+
+/***********************************/
+/*                                */
+/*     Command Substitution       */
+/*                                */
+/***********************************/
+
+static char *
+read_comsub (fd, quoted, rflag)
+     int fd, quoted;
+     int *rflag;
+{
+  char *istring, buf[128], *bufp, *s;
+  int istring_index, istring_size, c, tflag, skip_ctlesc, skip_ctlnul;
+  ssize_t bufn;
+
+  istring = (char *)NULL;
+  istring_index = istring_size = bufn = tflag = 0;
+
+  for (skip_ctlesc = skip_ctlnul = 0, s = ifs_value; s && *s; s++)
+    skip_ctlesc |= *s == CTLESC, skip_ctlnul |= *s == CTLNUL;
+
+#ifdef __CYGWIN__
+  setmode (fd, O_TEXT);                /* we don't want CR/LF, we want Unix-style */
+#endif
+
+  /* Read the output of the command through the pipe.  This may need to be
+     changed to understand multibyte characters in the future. */
+  while (1)
+    {
+      if (fd < 0)
+       break;
+      if (--bufn <= 0)
+       {
+         bufn = zread (fd, buf, sizeof (buf));
+         if (bufn <= 0) 
+           break;
+         bufp = buf;
+       }
+      c = *bufp++;
+
+      if (c == 0)
+       {
+#if 0
+         internal_warning ("read_comsub: ignored null byte in input");
+#endif
+         continue;
+       }
+
+      /* Add the character to ISTRING, possibly after resizing it. */
+      RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size, DEFAULT_ARRAY_SIZE);
+
+      /* This is essentially quote_string inline */
+      if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) /* || c == CTLESC || c == CTLNUL */)
+       istring[istring_index++] = CTLESC;
+      /* Escape CTLESC and CTLNUL in the output to protect those characters
+        from the rest of the word expansions (word splitting and globbing.)
+        This is essentially quote_escapes inline. */
+      else if (skip_ctlesc == 0 && c == CTLESC)
+       {
+         tflag |= W_HASCTLESC;
+         istring[istring_index++] = CTLESC;
+       }
+      else if ((skip_ctlnul == 0 && c == CTLNUL) || (c == ' ' && (ifs_value && *ifs_value == 0)))
+       istring[istring_index++] = CTLESC;
+
+      istring[istring_index++] = c;
+
+#if 0
+#if defined (__CYGWIN__)
+      if (c == '\n' && istring_index > 1 && istring[istring_index - 2] == '\r')
+       {
+         istring_index--;
+         istring[istring_index - 1] = '\n';
+       }
+#endif
+#endif
+    }
+
+  if (istring)
+    istring[istring_index] = '\0';
+
+  /* If we read no output, just return now and save ourselves some
+     trouble. */
+  if (istring_index == 0)
+    {
+      FREE (istring);
+      if (rflag)
+       *rflag = tflag;
+      return (char *)NULL;
+    }
+
+  /* Strip trailing newlines from the output of the command. */
+  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    {
+      while (istring_index > 0)
+       {
+         if (istring[istring_index - 1] == '\n')
+           {
+             --istring_index;
+
+             /* If the newline was quoted, remove the quoting char. */
+             if (istring[istring_index - 1] == CTLESC)
+               --istring_index;
+           }
+         else
+           break;
+       }
+      istring[istring_index] = '\0';
+    }
+  else
+    strip_trailing (istring, istring_index - 1, 1);
+
+  if (rflag)
+    *rflag = tflag;
+  return istring;
+}
+
+/* Perform command substitution on STRING.  This returns a WORD_DESC * with the
+   contained string possibly quoted. */
+WORD_DESC *
+command_substitute (string, quoted)
+     char *string;
+     int quoted;
+{
+  pid_t pid, old_pid, old_pipeline_pgrp, old_async_pid;
+  char *istring;
+  int result, fildes[2], function_value, pflags, rc, tflag;
+  WORD_DESC *ret;
+
+  istring = (char *)NULL;
+
+  /* Don't fork () if there is no need to.  In the case of no command to
+     run, just return NULL. */
+  if (!string || !*string || (string[0] == '\n' && !string[1]))
+    return ((WORD_DESC *)NULL);
+
+  if (wordexp_only && read_but_dont_execute)
+    {
+      last_command_exit_value = EX_WEXPCOMSUB;
+      jump_to_top_level (EXITPROG);
+    }
+
+  /* We're making the assumption here that the command substitution will
+     eventually run a command from the file system.  Since we'll run
+     maybe_make_export_env in this subshell before executing that command,
+     the parent shell and any other shells it starts will have to remake
+     the environment.  If we make it before we fork, other shells won't
+     have to.  Don't bother if we have any temporary variable assignments,
+     though, because the export environment will be remade after this
+     command completes anyway, but do it if all the words to be expanded
+     are variable assignments. */
+  if (subst_assign_varlist == 0 || garglist == 0)
+    maybe_make_export_env ();  /* XXX */
+
+  /* Flags to pass to parse_and_execute() */
+  pflags = (interactive && sourcelevel == 0) ? SEVAL_RESETLINE : 0;
+
+  /* Pipe the output of executing STRING into the current shell. */
+  if (pipe (fildes) < 0)
+    {
+      sys_error (_("cannot make pipe for command substitution"));
+      goto error_exit;
+    }
+
+  old_pid = last_made_pid;
+#if defined (JOB_CONTROL)
+  old_pipeline_pgrp = pipeline_pgrp;
+  /* Don't reset the pipeline pgrp if we're already a subshell in a pipeline. */
+  if ((subshell_environment & SUBSHELL_PIPE) == 0)
+    pipeline_pgrp = shell_pgrp;
+  cleanup_the_pipeline ();
+#endif /* JOB_CONTROL */
+
+  old_async_pid = last_asynchronous_pid;
+  pid = make_child ((char *)NULL, subshell_environment&SUBSHELL_ASYNC);
+  last_asynchronous_pid = old_async_pid;
+
+  if (pid == 0)
+    {
+      /* Reset the signal handlers in the child, but don't free the
+        trap strings.  Set a flag noting that we have to free the
+        trap strings if we run trap to change a signal disposition. */
+      reset_signal_handlers ();
+      subshell_environment |= SUBSHELL_RESETTRAP;
+    }
+
+#if defined (JOB_CONTROL)
+  /* XXX DO THIS ONLY IN PARENT ? XXX */
+  set_sigchld_handler ();
+  stop_making_children ();
+  if (pid != 0)
+    pipeline_pgrp = old_pipeline_pgrp;
+#else
+  stop_making_children ();
+#endif /* JOB_CONTROL */
+
+  if (pid < 0)
+    {
+      sys_error (_("cannot make child for command substitution"));
+    error_exit:
+
+      FREE (istring);
+      close (fildes[0]);
+      close (fildes[1]);
+      return ((WORD_DESC *)NULL);
+    }
+
+  if (pid == 0)
+    {
+      set_sigint_handler ();   /* XXX */
+
+      free_pushed_string_input ();
+
+      if (dup2 (fildes[1], 1) < 0)
+       {
+         sys_error (_("command_substitute: cannot duplicate pipe as fd 1"));
+         exit (EXECUTION_FAILURE);
+       }
+
+      /* If standard output is closed in the parent shell
+        (such as after `exec >&-'), file descriptor 1 will be
+        the lowest available file descriptor, and end up in
+        fildes[0].  This can happen for stdin and stderr as well,
+        but stdout is more important -- it will cause no output
+        to be generated from this command. */
+      if ((fildes[1] != fileno (stdin)) &&
+         (fildes[1] != fileno (stdout)) &&
+         (fildes[1] != fileno (stderr)))
+       close (fildes[1]);
+
+      if ((fildes[0] != fileno (stdin)) &&
+         (fildes[0] != fileno (stdout)) &&
+         (fildes[0] != fileno (stderr)))
+       close (fildes[0]);
+
+      /* The currently executing shell is not interactive. */
+      interactive = 0;
+
+      /* This is a subshell environment. */
+      subshell_environment |= SUBSHELL_COMSUB;
+
+      /* When not in POSIX mode, command substitution does not inherit
+        the -e flag. */
+      if (posixly_correct == 0)
+       exit_immediately_on_error = 0;
+
+      remove_quoted_escapes (string);
+
+      startup_state = 2;       /* see if we can avoid a fork */
+      /* Give command substitution a place to jump back to on failure,
+        so we don't go back up to main (). */
+      result = setjmp (top_level);
+
+      /* If we're running a command substitution inside a shell function,
+        trap `return' so we don't return from the function in the subshell
+        and go off to never-never land. */
+      if (result == 0 && return_catch_flag)
+       function_value = setjmp (return_catch);
+      else
+       function_value = 0;
+
+      if (result == ERREXIT)
+       rc = last_command_exit_value;
+      else if (result == EXITPROG)
+       rc = last_command_exit_value;
+      else if (result)
+       rc = EXECUTION_FAILURE;
+      else if (function_value)
+       rc = return_catch_value;
+      else
+       {
+         subshell_level++;
+         rc = parse_and_execute (string, "command substitution", pflags|SEVAL_NOHIST);
+         subshell_level--;
+       }
+
+      last_command_exit_value = rc;
+      rc = run_exit_trap ();
+#if defined (PROCESS_SUBSTITUTION)
+      unlink_fifo_list ();
+#endif
+      exit (rc);
+    }
+  else
+    {
+#if defined (JOB_CONTROL) && defined (PGRP_PIPE)
+      close_pgrp_pipe ();
+#endif /* JOB_CONTROL && PGRP_PIPE */
+
+      close (fildes[1]);
+
+      tflag = 0;
+      istring = read_comsub (fildes[0], quoted, &tflag);
+
+      close (fildes[0]);
+
+      current_command_subst_pid = pid;
+      last_command_exit_value = wait_for (pid);
+      last_command_subst_pid = pid;
+      last_made_pid = old_pid;
+
+#if defined (JOB_CONTROL)
+      /* If last_command_exit_value > 128, then the substituted command
+        was terminated by a signal.  If that signal was SIGINT, then send
+        SIGINT to ourselves.  This will break out of loops, for instance. */
+      if (last_command_exit_value == (128 + SIGINT) && last_command_exit_signal == SIGINT)
+       kill (getpid (), SIGINT);
+
+      /* wait_for gives the terminal back to shell_pgrp.  If some other
+        process group should have it, give it away to that group here.
+        pipeline_pgrp is non-zero only while we are constructing a
+        pipline, so what we are concerned about is whether or not that
+        pipeline was started in the background.  A pipeline started in
+        the background should never get the tty back here. */
+      if (interactive && pipeline_pgrp != (pid_t)0 && (subshell_environment & SUBSHELL_ASYNC) == 0)
+       give_terminal_to (pipeline_pgrp, 0);
+#endif /* JOB_CONTROL */
+
+      ret = alloc_word_desc ();
+      ret->word = istring;
+      ret->flags = tflag;
+
+      return ret;
+    }
+}
+
+/********************************************************
+ *                                                     *
+ *     Utility functions for parameter expansion       *
+ *                                                     *
+ ********************************************************/
+
+#if defined (ARRAY_VARS)
+
+static arrayind_t
+array_length_reference (s)
+     char *s;
+{
+  int len;
+  arrayind_t ind;
+  char *akey;
+  char *t, c;
+  ARRAY *array;
+  HASH_TABLE *h;
+  SHELL_VAR *var;
+
+  var = array_variable_part (s, &t, &len);
+
+  /* If unbound variables should generate an error, report one and return
+     failure. */
+  if ((var == 0 || (assoc_p (var) == 0 && array_p (var) == 0)) && unbound_vars_is_error)
+    {
+      c = *--t;
+      *t = '\0';
+      last_command_exit_value = EXECUTION_FAILURE;
+      err_unboundvar (s);
+      *t = c;
+      return (-1);
+    }
+  else if (var == 0)
+    return 0;
+
+  /* We support a couple of expansions for variables that are not arrays.
+     We'll return the length of the value for v[0], and 1 for v[@] or
+     v[*].  Return 0 for everything else. */
+
+  array = array_p (var) ? array_cell (var) : (ARRAY *)NULL;
+  h = assoc_p (var) ? assoc_cell (var) : (HASH_TABLE *)NULL;
+
+  if (ALL_ELEMENT_SUB (t[0]) && t[1] == ']')
+    {
+      if (assoc_p (var))
+       return (h ? assoc_num_elements (h) : 0);
+      else if (array_p (var))
+       return (array ? array_num_elements (array) : 0);
+      else
+       return (var_isset (var) ? 1 : 0);
+    }
+
+  if (assoc_p (var))
+    {
+      t[len - 1] = '\0';
+      akey = expand_assignment_string_to_string (t, 0);        /* [ */
+      t[len - 1] = ']';
+      if (akey == 0 || *akey == 0)
+       {
+         err_badarraysub (t);
+         return (-1);
+       }
+      t = assoc_reference (assoc_cell (var), akey);
+    }
+  else
+    {
+      ind = array_expand_index (t, len);
+      if (ind < 0)
+       {
+         err_badarraysub (t);
+         return (-1);
+       }
+      if (array_p (var))
+       t = array_reference (array, ind);
+      else
+       t = (ind == 0) ? value_cell (var) : (char *)NULL;
+    }
+
+  len = MB_STRLEN (t);
+  return (len);
+}
+#endif /* ARRAY_VARS */
+
+static int
+valid_brace_expansion_word (name, var_is_special)
+     char *name;
+     int var_is_special;
+{
+  if (DIGIT (*name) && all_digits (name))
+    return 1;
+  else if (var_is_special)
+    return 1;
+#if defined (ARRAY_VARS)
+  else if (valid_array_reference (name))
+    return 1;
+#endif /* ARRAY_VARS */
+  else if (legal_identifier (name))
+    return 1;
+  else
+    return 0;
+}
+
+static int
+chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at)
+     char *name;
+     int quoted;
+     int *quoted_dollar_atp, *contains_dollar_at;
+{
+  char *temp1;
+
+  if (name == 0)
+    {
+      if (quoted_dollar_atp)
+       *quoted_dollar_atp = 0;
+      if (contains_dollar_at)
+       *contains_dollar_at = 0;
+      return 0;
+    }
+
+  /* check for $@ and $* */
+  if (name[0] == '@' && name[1] == 0)
+    {
+      if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+       *quoted_dollar_atp = 1;
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+      return 1;
+    }
+  else if (name[0] == '*' && name[1] == '\0' && quoted == 0)
+    {
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+      return 1;
+    }
+
+  /* Now check for ${array[@]} and ${array[*]} */
+#if defined (ARRAY_VARS)
+  else if (valid_array_reference (name))
+    {
+      temp1 = mbschr (name, '[');
+      if (temp1 && temp1[1] == '@' && temp1[2] == ']')
+       {
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+           *quoted_dollar_atp = 1;
+         if (contains_dollar_at)
+           *contains_dollar_at = 1;
+         return 1;
+       }       /* [ */
+      /* ${array[*]}, when unquoted, should be treated like ${array[@]},
+        which should result in separate words even when IFS is unset. */
+      if (temp1 && temp1[1] == '*' && temp1[2] == ']' && quoted == 0)
+       {
+         if (contains_dollar_at)
+           *contains_dollar_at = 1;
+         return 1;
+       }
+    }
+#endif
+  return 0;
+}
+
+/* Parameter expand NAME, and return a new string which is the expansion,
+   or NULL if there was no expansion.
+   VAR_IS_SPECIAL is non-zero if NAME is one of the special variables in
+   the shell, e.g., "@", "$", "*", etc.  QUOTED, if non-zero, means that
+   NAME was found inside of a double-quoted expression. */
+static WORD_DESC *
+parameter_brace_expand_word (name, var_is_special, quoted, pflags, indp)
+     char *name;
+     int var_is_special, quoted, pflags;
+     arrayind_t *indp;
+{
+  WORD_DESC *ret;
+  char *temp, *tt;
+  intmax_t arg_index;
+  SHELL_VAR *var;
+  int atype, rflags;
+  arrayind_t ind;
+
+  ret = 0;
+  temp = 0;
+  rflags = 0;
+
+  if (indp)
+    *indp = INTMAX_MIN;
+
+  /* Handle multiple digit arguments, as in ${11}. */  
+  if (legal_number (name, &arg_index))
+    {
+      tt = get_dollar_var_value (arg_index);
+      if (tt)
+       temp = (*tt && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                 ? quote_string (tt)
+                 : quote_escapes (tt);
+      else
+        temp = (char *)NULL;
+      FREE (tt);
+    }
+  else if (var_is_special)      /* ${@} */
+    {
+      int sindex;
+      tt = (char *)xmalloc (2 + strlen (name));
+      tt[sindex = 0] = '$';
+      strcpy (tt + 1, name);
+
+      ret = param_expand (tt, &sindex, quoted, (int *)NULL, (int *)NULL,
+                         (int *)NULL, (int *)NULL, pflags);
+      free (tt);
+    }
+#if defined (ARRAY_VARS)
+  else if (valid_array_reference (name))
+    {
+      temp = array_value (name, quoted, 0, &atype, &ind);
+      if (atype == 0 && temp)
+       {
+         temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                   ? quote_string (temp)
+                   : quote_escapes (temp);
+         rflags |= W_ARRAYIND;
+         if (indp)
+           *indp = ind;
+       }                 
+      else if (atype == 1 && temp && QUOTED_NULL (temp) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+       rflags |= W_HASQUOTEDNULL;
+    }
+#endif
+  else if (var = find_variable (name))
+    {
+      if (var_isset (var) && invisible_p (var) == 0)
+       {
+#if defined (ARRAY_VARS)
+         if (assoc_p (var))
+           temp = assoc_reference (assoc_cell (var), "0");
+         else if (array_p (var))
+           temp = array_reference (array_cell (var), 0);
+         else
+           temp = value_cell (var);
+#else
+         temp = value_cell (var);
+#endif
+
+         if (temp)
+           temp = (*temp && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+                     ? quote_string (temp)
+                     : quote_escapes (temp);
+       }
+      else
+       temp = (char *)NULL;
+    }
+  else
+    temp = (char *)NULL;
+
+  if (ret == 0)
+    {
+      ret = alloc_word_desc ();
+      ret->word = temp;
+      ret->flags |= rflags;
+    }
+  return ret;
+}
+
+/* Expand an indirect reference to a variable: ${!NAME} expands to the
+   value of the variable whose name is the value of NAME. */
+static WORD_DESC *
+parameter_brace_expand_indir (name, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at)
+     char *name;
+     int var_is_special, quoted;
+     int *quoted_dollar_atp, *contains_dollar_at;
+{
+  char *temp, *t;
+  WORD_DESC *w;
+
+  w = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND, 0);
+  t = w->word;
+  /* Have to dequote here if necessary */
+  if (t)
+    {
+      temp = (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+               ? dequote_string (t)
+               : dequote_escapes (t);
+      free (t);
+      t = temp;
+    }
+  dispose_word_desc (w);
+
+  chk_atstar (t, quoted, quoted_dollar_atp, contains_dollar_at);
+  if (t == 0)
+    return (WORD_DESC *)NULL;
+
+  w = parameter_brace_expand_word (t, SPECIAL_VAR(t, 0), quoted, 0, 0);
+  free (t);
+
+  return w;
+}
+
+/* Expand the right side of a parameter expansion of the form ${NAMEcVALUE},
+   depending on the value of C, the separating character.  C can be one of
+   "-", "+", or "=".  QUOTED is true if the entire brace expression occurs
+   between double quotes. */
+static WORD_DESC *
+parameter_brace_expand_rhs (name, value, c, quoted, qdollaratp, hasdollarat)
+     char *name, *value;
+     int c, quoted, *qdollaratp, *hasdollarat;
+{
+  WORD_DESC *w;
+  WORD_LIST *l;
+  char *t, *t1, *temp;
+  int hasdol;
+
+  /* If the entire expression is between double quotes, we want to treat
+     the value as a double-quoted string, with the exception that we strip
+     embedded unescaped double quotes (for sh backwards compatibility). */
+  if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && *value)
+    {
+      hasdol = 0;
+      temp = string_extract_double_quoted (value, &hasdol, 1);
+    }
+  else
+    temp = value;
+
+  w = alloc_word_desc ();
+  hasdol = 0;
+  /* XXX was 0 not quoted */
+  l = *temp ? expand_string_for_rhs (temp, quoted, &hasdol, (int *)NULL)
+           : (WORD_LIST *)0;
+  if (hasdollarat)
+    *hasdollarat = hasdol || (l && l->next);
+  if (temp != value)
+    free (temp);
+  if (l)
+    {
+      /* The expansion of TEMP returned something.  We need to treat things
+         slightly differently if HASDOL is non-zero.  If we have "$@", the
+         individual words have already been quoted.  We need to turn them
+         into a string with the words separated by the first character of
+         $IFS without any additional quoting, so string_list_dollar_at won't
+         do the right thing.  We use string_list_dollar_star instead. */
+      temp = (hasdol || l->next) ? string_list_dollar_star (l) : string_list (l);
+
+      /* If l->next is not null, we know that TEMP contained "$@", since that
+        is the only expansion that creates more than one word. */
+      if (qdollaratp && ((hasdol && quoted) || l->next))
+       *qdollaratp = 1;
+      dispose_words (l);
+    }
+  else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && hasdol)
+    {
+      /* The brace expansion occurred between double quotes and there was
+        a $@ in TEMP.  It does not matter if the $@ is quoted, as long as
+        it does not expand to anything.  In this case, we want to return
+        a quoted empty string. */
+      temp = make_quoted_char ('\0');
+      w->flags |= W_HASQUOTEDNULL;
+    }
+  else
+    temp = (char *)NULL;
+
+  if (c == '-' || c == '+')
+    {
+      w->word = temp;
+      return w;
+    }
+
+  /* c == '=' */
+  t = temp ? savestring (temp) : savestring ("");
+  t1 = dequote_string (t);
+  free (t);
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (name))
+    assign_array_element (name, t1, 0);
+  else
+#endif /* ARRAY_VARS */
+  bind_variable (name, t1, 0);
+
+  /* From Posix group discussion Feb-March 2010.  Issue 7 0000221 */
+  free (temp);
+
+  w->word = t1;
+  return w;
+}
+
+/* Deal with the right hand side of a ${name:?value} expansion in the case
+   that NAME is null or not set.  If VALUE is non-null it is expanded and
+   used as the error message to print, otherwise a standard message is
+   printed. */
+static void
+parameter_brace_expand_error (name, value)
+     char *name, *value;
+{
+  WORD_LIST *l;
+  char *temp;
+
+  if (value && *value)
+    {
+      l = expand_string (value, 0);
+      temp =  string_list (l);
+      report_error ("%s: %s", name, temp ? temp : ""); /* XXX was value not "" */
+      FREE (temp);
+      dispose_words (l);
+    }
+  else
+    report_error (_("%s: parameter null or not set"), name);
+
+  /* Free the data we have allocated during this expansion, since we
+     are about to longjmp out. */
+  free (name);
+  FREE (value);
+}
+
+/* Return 1 if NAME is something for which parameter_brace_expand_length is
+   OK to do. */
+static int
+valid_length_expression (name)
+     char *name;
+{
+  return (name[1] == '\0' ||                                   /* ${#} */
+         ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0') ||  /* special param */
+         (DIGIT (name[1]) && all_digits (name + 1)) || /* ${#11} */
+#if defined (ARRAY_VARS)
+         valid_array_reference (name + 1) ||                   /* ${#a[7]} */
+#endif
+         legal_identifier (name + 1));                         /* ${#PS1} */
+}
+
+#if defined (HANDLE_MULTIBYTE)
+size_t
+mbstrlen (s)
+     const char *s;
+{
+  size_t clen, nc;
+  mbstate_t mbs, mbsbak;
+
+  nc = 0;
+  memset (&mbs, 0, sizeof (mbs));
+  mbsbak = mbs;
+  while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
+    {
+      if (MB_INVALIDCH(clen))
+        {
+         clen = 1;     /* assume single byte */
+         mbs = mbsbak;
+        }
+
+      s += clen;
+      nc++;
+      mbsbak = mbs;
+    }
+  return nc;
+}
+#endif
+      
+
+/* Handle the parameter brace expansion that requires us to return the
+   length of a parameter. */
+static intmax_t
+parameter_brace_expand_length (name)
+     char *name;
+{
+  char *t, *newname;
+  intmax_t number, arg_index;
+  WORD_LIST *list;
+#if defined (ARRAY_VARS)
+  SHELL_VAR *var;
+#endif
+
+  if (name[1] == '\0')                 /* ${#} */
+    number = number_of_args ();
+  else if ((name[1] == '@' || name[1] == '*') && name[2] == '\0')      /* ${#@}, ${#*} */
+    number = number_of_args ();
+  else if ((sh_syntaxtab[(unsigned char) name[1]] & CSPECVAR) && name[2] == '\0')
+    {
+      /* Take the lengths of some of the shell's special parameters. */
+      switch (name[1])
+       {
+       case '-':
+         t = which_set_flags ();
+         break;
+       case '?':
+         t = itos (last_command_exit_value);
+         break;
+       case '$':
+         t = itos (dollar_dollar_pid);
+         break;
+       case '!':
+         if (last_asynchronous_pid == NO_PID)
+           t = (char *)NULL;   /* XXX - error if set -u set? */
+         else
+           t = itos (last_asynchronous_pid);
+         break;
+       case '#':
+         t = itos (number_of_args ());
+         break;
+       }
+      number = STRLEN (t);
+      FREE (t);
+    }
+#if defined (ARRAY_VARS)
+  else if (valid_array_reference (name + 1))
+    number = array_length_reference (name + 1);
+#endif /* ARRAY_VARS */
+  else
+    {
+      number = 0;
+
+      if (legal_number (name + 1, &arg_index))         /* ${#1} */
+       {
+         t = get_dollar_var_value (arg_index);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
+         number = MB_STRLEN (t);
+         FREE (t);
+       }
+#if defined (ARRAY_VARS)
+      else if ((var = find_variable (name + 1)) && (invisible_p (var) == 0) && (array_p (var) || assoc_p (var)))
+       {
+         if (assoc_p (var))
+           t = assoc_reference (assoc_cell (var), "0");
+         else
+           t = array_reference (array_cell (var), 0);
+         if (t == 0 && unbound_vars_is_error)
+           return INTMAX_MIN;
+         number = MB_STRLEN (t);
+       }
+#endif
+      else                             /* ${#PS1} */
+       {
+         newname = savestring (name);
+         newname[0] = '$';
+         list = expand_string (newname, Q_DOUBLE_QUOTES);
+         t = list ? string_list (list) : (char *)NULL;
+         free (newname);
+         if (list)
+           dispose_words (list);
+
+         number = MB_STRLEN (t);
+         FREE (t);
+       }
+    }
+
+  return (number);
+}
+
+/* Skip characters in SUBSTR until DELIM.  SUBSTR is an arithmetic expression,
+   so we do some ad-hoc parsing of an arithmetic expression to find
+   the first DELIM, instead of using strchr(3).  Two rules:
+       1.  If the substring contains a `(', read until closing `)'.
+       2.  If the substring contains a `?', read past one `:' for each `?'.
+*/
+
+static char *
+skiparith (substr, delim)
+     char *substr;
+     int delim;
+{
+  size_t sublen;
+  int skipcol, pcount, i;
+  DECLARE_MBSTATE;
+
+  sublen = strlen (substr);
+  i = skipcol = pcount = 0;
+  while (substr[i])
+    {
+      /* Balance parens */
+      if (substr[i] == LPAREN)
+       {
+         pcount++;
+         i++;
+         continue;
+       }
+      if (substr[i] == RPAREN && pcount)
+       {
+         pcount--;
+         i++;
+         continue;
+       }
+      if (pcount)
+       {
+         ADVANCE_CHAR (substr, sublen, i);
+         continue;
+       }
+
+      /* Skip one `:' for each `?' */
+      if (substr[i] == ':' && skipcol)
+       {
+         skipcol--;
+         i++;
+         continue;
+       }
+      if (substr[i] == delim)
+       break;
+      if (substr[i] == '?')
+       {
+         skipcol++;
+         i++;
+         continue;
+       }
+      ADVANCE_CHAR (substr, sublen, i);
+    }
+
+  return (substr + i);
+}
+
+/* Verify and limit the start and end of the desired substring.  If
+   VTYPE == 0, a regular shell variable is being used; if it is 1,
+   then the positional parameters are being used; if it is 2, then
+   VALUE is really a pointer to an array variable that should be used.
+   Return value is 1 if both values were OK, 0 if there was a problem
+   with an invalid expression, or -1 if the values were out of range. */
+static int
+verify_substring_values (v, value, substr, vtype, e1p, e2p)
+     SHELL_VAR *v;
+     char *value, *substr;
+     int vtype;
+     intmax_t *e1p, *e2p;
+{
+  char *t, *temp1, *temp2;
+  arrayind_t len;
+  int expok;
+#if defined (ARRAY_VARS)
+ ARRAY *a;
+ HASH_TABLE *h;
+#endif
+
+  /* duplicate behavior of strchr(3) */
+  t = skiparith (substr, ':');
+  if (*t && *t == ':')
+    *t = '\0';
+  else
+    t = (char *)0;
+
+  temp1 = expand_arith_string (substr, Q_DOUBLE_QUOTES);
+  *e1p = evalexp (temp1, &expok);
+  free (temp1);
+  if (expok == 0)
+    return (0);
+
+  len = -1;    /* paranoia */
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      len = MB_STRLEN (value);
+      break;
+    case VT_POSPARMS:
+      len = number_of_args () + 1;
+      if (*e1p == 0)
+       len++;          /* add one arg if counting from $0 */
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      /* For arrays, the first value deals with array indices.  Negative
+        offsets count from one past the array's maximum index.  Associative
+        arrays treat the number of elements as the maximum index. */
+      if (assoc_p (v))
+       {
+         h = assoc_cell (v);
+         len = assoc_num_elements (h) + (*e1p < 0);
+       }
+      else
+       {
+         a = (ARRAY *)value;
+         len = array_max_index (a) + (*e1p < 0);       /* arrays index from 0 to n - 1 */
+       }
+      break;
+#endif
+    }
+
+  if (len == -1)       /* paranoia */
+    return -1;
+
+  if (*e1p < 0)                /* negative offsets count from end */
+    *e1p += len;
+
+  if (*e1p > len || *e1p < 0)
+    return (-1);
+
+#if defined (ARRAY_VARS)
+  /* For arrays, the second offset deals with the number of elements. */
+  if (vtype == VT_ARRAYVAR)
+    len = assoc_p (v) ? assoc_num_elements (h) : array_num_elements (a);
+#endif
+
+  if (t)
+    {
+      t++;
+      temp2 = savestring (t);
+      temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES);
+      free (temp2);
+      t[-1] = ':';
+      *e2p = evalexp (temp1, &expok);
+      free (temp1);
+      if (expok == 0)
+       return (0);
+      if ((vtype == VT_ARRAYVAR || vtype == VT_POSPARMS) && *e2p < 0)
+       {
+         internal_error (_("%s: substring expression < 0"), t);
+         return (0);
+       }
+#if defined (ARRAY_VARS)
+      /* In order to deal with sparse arrays, push the intelligence about how
+        to deal with the number of elements desired down to the array-
+        specific functions.  */
+      if (vtype != VT_ARRAYVAR)
+#endif
+       {
+         if (*e2p < 0)
+           {
+             *e2p += len;
+             if (*e2p < 0 || *e2p < *e1p)
+               {
+                 internal_error (_("%s: substring expression < 0"), t);
+                 return (0);
+               }
+           }
+         else
+           *e2p += *e1p;               /* want E2 chars starting at E1 */
+         if (*e2p > len)
+           *e2p = len;
+       }
+    }
+  else
+    *e2p = len;
+
+  return (1);
+}
+
+/* Return the type of variable specified by VARNAME (simple variable,
+   positional param, or array variable).  Also return the value specified
+   by VARNAME (value of a variable or a reference to an array element).
+   QUOTED is the standard description of quoting state, using Q_* defines.
+   FLAGS is currently a set of flags to pass to array_value.  If IND is
+   non-null and not INTMAX_MIN, and FLAGS includes AV_USEIND, IND is
+   passed to array_value so the array index is not computed again.
+   If this returns VT_VARIABLE, the caller assumes that CTLESC and CTLNUL
+   characters in the value are quoted with CTLESC and takes appropriate
+   steps.  For convenience, *VALP is set to the dequoted VALUE. */
+static int
+get_var_and_type (varname, value, ind, quoted, flags, varp, valp)
+     char *varname, *value;
+     arrayind_t ind;
+     int quoted, flags;
+     SHELL_VAR **varp;
+     char **valp;
+{
+  int vtype;
+  char *temp;
+#if defined (ARRAY_VARS)
+  SHELL_VAR *v;
+#endif
+  arrayind_t lind;
+
+  /* This sets vtype to VT_VARIABLE or VT_POSPARMS */
+  vtype = (varname[0] == '@' || varname[0] == '*') && varname[1] == '\0';
+  if (vtype == VT_POSPARMS && varname[0] == '*')
+    vtype |= VT_STARSUB;
+  *varp = (SHELL_VAR *)NULL;
+
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (varname))
+    {
+      v = array_variable_part (varname, &temp, (int *)0);
+      /* If we want to signal array_value to use an already-computed index,
+        set LIND to that index */
+      lind = (ind != INTMAX_MIN && (flags & AV_USEIND)) ? ind : 0;
+      if (v && (array_p (v) || assoc_p (v)))
+       { /* [ */
+         if (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']')
+           {
+             /* Callers have to differentiate betwen indexed and associative */
+             vtype = VT_ARRAYVAR;
+             if (temp[0] == '*')
+               vtype |= VT_STARSUB;
+             *valp = array_p (v) ? (char *)array_cell (v) : (char *)assoc_cell (v);
+           }
+         else
+           {
+             vtype = VT_ARRAYMEMBER;
+             *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+           }
+         *varp = v;
+       }
+      else if (v && (ALL_ELEMENT_SUB (temp[0]) && temp[1] == ']'))
+       {
+         vtype = VT_VARIABLE;
+         *varp = v;
+         if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+           *valp = dequote_string (value);
+         else
+           *valp = dequote_escapes (value);
+       }
+      else
+       {
+         vtype = VT_ARRAYMEMBER;
+         *varp = v;
+         *valp = array_value (varname, Q_DOUBLE_QUOTES, flags, (int *)NULL, &lind);
+       }
+    }
+  else if ((v = find_variable (varname)) && (invisible_p (v) == 0) && (assoc_p (v) || array_p (v)))
+    {
+      vtype = VT_ARRAYMEMBER;
+      *varp = v;
+      *valp = assoc_p (v) ? assoc_reference (assoc_cell (v), "0") : array_reference (array_cell (v), 0);
+    }
+  else
+#endif
+    {
+      if (value && vtype == VT_VARIABLE)
+       {
+         if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+           *valp = dequote_string (value);
+         else
+           *valp = dequote_escapes (value);
+       }
+      else
+       *valp = value;
+    }
+
+  return vtype;
+}
+
+/******************************************************/
+/*                                                   */
+/* Functions to extract substrings of variable values */
+/*                                                   */
+/******************************************************/
+
+#if defined (HANDLE_MULTIBYTE)
+/* Character-oriented rather than strictly byte-oriented substrings.  S and
+   E, rather being strict indices into STRING, indicate character (possibly
+   multibyte character) positions that require calculation.
+   Used by the ${param:offset[:length]} expansion. */
+static char *
+mb_substring (string, s, e)
+     char *string;
+     int s, e;
+{
+  char *tt;
+  int start, stop, i, slen;
+  DECLARE_MBSTATE;
+
+  start = 0;
+  /* Don't need string length in ADVANCE_CHAR unless multibyte chars possible. */
+  slen = (MB_CUR_MAX > 1) ? STRLEN (string) : 0;
+
+  i = s;
+  while (string[start] && i--)
+    ADVANCE_CHAR (string, slen, start);
+  stop = start;
+  i = e - s;
+  while (string[stop] && i--)
+    ADVANCE_CHAR (string, slen, stop);
+  tt = substring (string, start, stop);
+  return tt;
+}
+#endif
+  
+/* Process a variable substring expansion: ${name:e1[:e2]}.  If VARNAME
+   is `@', use the positional parameters; otherwise, use the value of
+   VARNAME.  If VARNAME is an array variable, use the array elements. */
+
+static char *
+parameter_brace_substring (varname, value, ind, substr, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *substr;
+     int quoted, flags;
+{
+  intmax_t e1, e2;
+  int vtype, r, starsub;
+  char *temp, *val, *tt, *oname;
+  SHELL_VAR *v;
+
+  if (value == 0)
+    return ((char *)NULL);
+
+  oname = this_command_name;
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    {
+      this_command_name = oname;
+      return ((char *)NULL);
+    }
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  r = verify_substring_values (v, val, substr, vtype, &e1, &e2);
+  this_command_name = oname;
+  if (r <= 0)
+    return ((r == 0) ? &expand_param_error : (char *)NULL);
+
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+#if defined (HANDLE_MULTIBYTE)
+      if (MB_CUR_MAX > 1)
+       tt = mb_substring (val, e1, e2);
+      else
+#endif
+      tt = substring (val, e1, e2);
+
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT))
+       temp = quote_string (tt);
+      else
+       temp = tt ? quote_escapes (tt) : (char *)NULL;
+      FREE (tt);
+      break;
+    case VT_POSPARMS:
+      tt = pos_params (varname, e1, e2, quoted);
+      if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
+       {
+         temp = tt ? quote_escapes (tt) : (char *)NULL;
+         FREE (tt);
+       }
+      else
+       temp = tt;
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      if (assoc_p (v))
+       /* we convert to list and take first e2 elements starting at e1th
+          element -- officially undefined for now */   
+       temp = assoc_subrange (assoc_cell (v), e1, e2, starsub, quoted);
+      else
+      /* We want E2 to be the number of elements desired (arrays can be sparse,
+        so verify_substring_values just returns the numbers specified and we
+        rely on array_subrange to understand how to deal with them). */
+       temp = array_subrange (array_cell (v), e1, e2, starsub, quoted);
+      /* array_subrange now calls array_quote_escapes as appropriate, so the
+        caller no longer needs to. */
+      break;
+#endif
+    default:
+      temp = (char *)NULL;
+    }
+
+  return temp;
+}
+
+/****************************************************************/
+/*                                                             */
+/* Functions to perform pattern substitution on variable values */
+/*                                                             */
+/****************************************************************/
+
+static int
+shouldexp_replacement (s)
+     char *s;
+{
+  register char *p;
+
+  for (p = s; p && *p; p++)
+    {
+      if (*p == '\\')
+       p++;
+      else if (*p == '&')
+       return 1;
+    }
+  return 0;
+}
+
+char *
+pat_subst (string, pat, rep, mflags)
+     char *string, *pat, *rep;
+     int mflags;
+{
+  char *ret, *s, *e, *str, *rstr, *mstr;
+  int rsize, rptr, l, replen, mtype, rxpand, rslen, mlen;
+
+  if (string  == 0)
+    return (savestring (""));
+
+  mtype = mflags & MATCH_TYPEMASK;
+
+#if 0  /* bash-4.2 ? */
+  rxpand = (rep && *rep) ? shouldexp_replacement (rep) : 0;
+#else
+  rxpand = 0;
+#endif
+
+  /* Special cases:
+   *   1.  A null pattern with mtype == MATCH_BEG means to prefix STRING
+   *       with REP and return the result.
+   *   2.  A null pattern with mtype == MATCH_END means to append REP to
+   *       STRING and return the result.
+   * These don't understand or process `&' in the replacement string.
+   */
+  if ((pat == 0 || *pat == 0) && (mtype == MATCH_BEG || mtype == MATCH_END))
+    {
+      replen = STRLEN (rep);
+      l = STRLEN (string);
+      ret = (char *)xmalloc (replen + l + 2);
+      if (replen == 0)
+       strcpy (ret, string);
+      else if (mtype == MATCH_BEG)
+       {
+         strcpy (ret, rep);
+         strcpy (ret + replen, string);
+       }
+      else
+       {
+         strcpy (ret, string);
+         strcpy (ret + l, rep);
+       }
+      return (ret);
+    }
+
+  ret = (char *)xmalloc (rsize = 64);
+  ret[0] = '\0';
+
+  for (replen = STRLEN (rep), rptr = 0, str = string;;)
+    {
+      if (match_pattern (str, pat, mtype, &s, &e) == 0)
+       break;
+      l = s - str;
+
+      if (rxpand)
+        {
+          int x;
+          mlen = e - s;
+          mstr = xmalloc (mlen + 1);
+         for (x = 0; x < mlen; x++)
+           mstr[x] = s[x];
+          mstr[mlen] = '\0';
+          rstr = strcreplace (rep, '&', mstr, 0);
+          rslen = strlen (rstr);
+        }
+      else
+        {
+          rstr = rep;
+          rslen = replen;
+        }
+        
+      RESIZE_MALLOCED_BUFFER (ret, rptr, (l + rslen), rsize, 64);
+
+      /* OK, now copy the leading unmatched portion of the string (from
+        str to s) to ret starting at rptr (the current offset).  Then copy
+        the replacement string at ret + rptr + (s - str).  Increment
+        rptr (if necessary) and str and go on. */
+      if (l)
+       {
+         strncpy (ret + rptr, str, l);
+         rptr += l;
+       }
+      if (replen)
+       {
+         strncpy (ret + rptr, rstr, rslen);
+         rptr += rslen;
+       }
+      str = e;         /* e == end of match */
+
+      if (rstr != rep)
+       free (rstr);
+
+      if (((mflags & MATCH_GLOBREP) == 0) || mtype != MATCH_ANY)
+       break;
+
+      if (s == e)
+       {
+         /* On a zero-length match, make sure we copy one character, since
+            we increment one character to avoid infinite recursion. */
+         RESIZE_MALLOCED_BUFFER (ret, rptr, 1, rsize, 64);
+         ret[rptr++] = *str++;
+         e++;          /* avoid infinite recursion on zero-length match */
+       }
+    }
+
+  /* Now copy the unmatched portion of the input string */
+  if (str && *str)
+    {
+      RESIZE_MALLOCED_BUFFER (ret, rptr, STRLEN(str) + 1, rsize, 64);
+      strcpy (ret + rptr, str);
+    }
+  else
+    ret[rptr] = '\0';
+
+  return ret;
+}
+
+/* Do pattern match and replacement on the positional parameters. */
+static char *
+pos_params_pat_subst (string, pat, rep, mflags)
+     char *string, *pat, *rep;
+     int mflags;
+{
+  WORD_LIST *save, *params;
+  WORD_DESC *w;
+  char *ret;
+  int pchar, qflags;
+
+  save = params = list_rest_of_args ();
+  if (save == 0)
+    return ((char *)NULL);
+
+  for ( ; params; params = params->next)
+    {
+      ret = pat_subst (params->word->word, pat, rep, mflags);
+      w = alloc_word_desc ();
+      w->word = ret ? ret : savestring ("");
+      dispose_word (params->word);
+      params->word = w;
+    }
+
+  pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+  qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+#if 0
+  if ((mflags & (MATCH_QUOTED|MATCH_STARSUB)) == (MATCH_QUOTED|MATCH_STARSUB))
+    ret = string_list_dollar_star (quote_list (save));
+  else if ((mflags & MATCH_STARSUB) == MATCH_STARSUB)
+    ret = string_list_dollar_star (save);
+  else if ((mflags & MATCH_QUOTED) == MATCH_QUOTED)
+    ret = string_list_dollar_at (save, qflags);
+  else
+    ret = string_list_dollar_star (save);
+#else
+  ret = string_list_pos_params (pchar, save, qflags);
+#endif
+
+  dispose_words (save);
+
+  return (ret);
+}
+
+/* Perform pattern substitution on VALUE, which is the expansion of
+   VARNAME.  PATSUB is an expression supplying the pattern to match
+   and the string to substitute.  QUOTED is a flags word containing
+   the type of quoting currently in effect. */
+static char *
+parameter_brace_patsub (varname, value, ind, patsub, quoted, flags)
+     char *varname, *value;
+     int ind;
+     char *patsub;
+     int quoted, flags;
+{
+  int vtype, mflags, starsub, delim;
+  char *val, *temp, *pat, *rep, *p, *lpatsub, *tt;
+  SHELL_VAR *v;
+
+  if (value == 0)
+    return ((char *)NULL);
+
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    return ((char *)NULL);
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  mflags = 0;
+  if (patsub && *patsub == '/')
+    {
+      mflags |= MATCH_GLOBREP;
+      patsub++;
+    }
+
+  /* Malloc this because expand_string_if_necessary or one of the expansion
+     functions in its call chain may free it on a substitution error. */
+  lpatsub = savestring (patsub);
+
+  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    mflags |= MATCH_QUOTED;
+
+  if (starsub)
+    mflags |= MATCH_STARSUB;
+
+  /* If the pattern starts with a `/', make sure we skip over it when looking
+     for the replacement delimiter. */
+#if 0
+  if (rep = quoted_strchr ((*patsub == '/') ? lpatsub+1 : lpatsub, '/', ST_BACKSL))
+    *rep++ = '\0';
+  else
+    rep = (char *)NULL;
+#else
+  delim = skip_to_delim (lpatsub, ((*patsub == '/') ? 1 : 0), "/", 0);
+  if (lpatsub[delim] == '/')
+    {
+      lpatsub[delim] = 0;
+      rep = lpatsub + delim + 1;
+    }
+  else
+    rep = (char *)NULL;
+#endif
+
+  if (rep && *rep == '\0')
+    rep = (char *)NULL;
+
+  /* Perform the same expansions on the pattern as performed by the
+     pattern removal expansions. */
+  pat = getpattern (lpatsub, quoted, 1);
+
+  if (rep)
+    {
+      if ((mflags & MATCH_QUOTED) == 0)
+       rep = expand_string_if_necessary (rep, quoted, expand_string_unsplit);
+      else
+       rep = expand_string_to_string_internal (rep, quoted, expand_string_unsplit);
+    }
+
+  /* ksh93 doesn't allow the match specifier to be a part of the expanded
+     pattern.  This is an extension.  Make sure we don't anchor the pattern
+     at the beginning or end of the string if we're doing global replacement,
+     though. */
+  p = pat;
+  if (mflags & MATCH_GLOBREP)
+    mflags |= MATCH_ANY;
+  else if (pat && pat[0] == '#')
+    {
+      mflags |= MATCH_BEG;
+      p++;
+    }
+  else if (pat && pat[0] == '%')
+    {
+      mflags |= MATCH_END;
+      p++;
+    }
+  else
+    mflags |= MATCH_ANY;
+
+  /* OK, we now want to substitute REP for PAT in VAL.  If
+     flags & MATCH_GLOBREP is non-zero, the substitution is done
+     everywhere, otherwise only the first occurrence of PAT is
+     replaced.  The pattern matching code doesn't understand
+     CTLESC quoting CTLESC and CTLNUL so we use the dequoted variable
+     values passed in (VT_VARIABLE) so the pattern substitution
+     code works right.  We need to requote special chars after
+     we're done for VT_VARIABLE and VT_ARRAYMEMBER, and for the
+     other cases if QUOTED == 0, since the posparams and arrays
+     indexed by * or @ do special things when QUOTED != 0. */
+
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      temp = pat_subst (val, p, rep, mflags);
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (temp)
+       {
+         tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp);
+         free (temp);
+         temp = tt;
+       }
+      break;
+    case VT_POSPARMS:
+      temp = pos_params_pat_subst (val, p, rep, mflags);
+      if (temp && (mflags & MATCH_QUOTED) == 0)
+       {
+         tt = quote_escapes (temp);
+         free (temp);
+         temp = tt;
+       }
+      break;
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      temp = assoc_p (v) ? assoc_patsub (assoc_cell (v), p, rep, mflags)
+                        : array_patsub (array_cell (v), p, rep, mflags);
+      /* Don't call quote_escapes anymore; array_patsub calls
+        array_quote_escapes as appropriate before adding the
+        space separators; ditto for assoc_patsub. */
+      break;
+#endif
+    }
+
+  FREE (pat);
+  FREE (rep);
+  free (lpatsub);
+
+  return temp;
+}
+
+/****************************************************************/
+/*                                                             */
+/*   Functions to perform case modification on variable values  */
+/*                                                             */
+/****************************************************************/
+
+/* Do case modification on the positional parameters. */
+
+static char *
+pos_params_modcase (string, pat, modop, mflags)
+     char *string, *pat;
+     int modop;
+     int mflags;
+{
+  WORD_LIST *save, *params;
+  WORD_DESC *w;
+  char *ret;
+  int pchar, qflags;
+
+  save = params = list_rest_of_args ();
+  if (save == 0)
+    return ((char *)NULL);
+
+  for ( ; params; params = params->next)
+    {
+      ret = sh_modcase (params->word->word, pat, modop);
+      w = alloc_word_desc ();
+      w->word = ret ? ret : savestring ("");
+      dispose_word (params->word);
+      params->word = w;
+    }
+
+  pchar = (mflags & MATCH_STARSUB) == MATCH_STARSUB ? '*' : '@';
+  qflags = (mflags & MATCH_QUOTED) == MATCH_QUOTED ? Q_DOUBLE_QUOTES : 0;
+
+  ret = string_list_pos_params (pchar, save, qflags);
+  dispose_words (save);
+
+  return (ret);
+}
+
+/* Perform case modification on VALUE, which is the expansion of
+   VARNAME.  MODSPEC is an expression supplying the type of modification
+   to perform.  QUOTED is a flags word containing the type of quoting
+   currently in effect. */
+static char *
+parameter_brace_casemod (varname, value, ind, modspec, patspec, quoted, flags)
+     char *varname, *value;
+     int ind, modspec;
+     char *patspec;
+     int quoted, flags;
+{
+  int vtype, starsub, modop, mflags, x;
+  char *val, *temp, *pat, *p, *lpat, *tt;
+  SHELL_VAR *v;
+
+  if (value == 0)
+    return ((char *)NULL);
+
+  this_command_name = varname;
+
+  vtype = get_var_and_type (varname, value, ind, quoted, flags, &v, &val);
+  if (vtype == -1)
+    return ((char *)NULL);
+
+  starsub = vtype & VT_STARSUB;
+  vtype &= ~VT_STARSUB;
+
+  modop = 0;
+  mflags = 0;
+  if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+    mflags |= MATCH_QUOTED;
+  if (starsub)
+    mflags |= MATCH_STARSUB;
+  
+  p = patspec;
+  if (modspec == '^')
+    {
+      x = p && p[0] == modspec;
+      modop = x ? CASE_UPPER : CASE_UPFIRST;
+      p += x;
+    }
+  else if (modspec == ',')
+    {
+      x = p && p[0] == modspec;
+      modop = x ? CASE_LOWER : CASE_LOWFIRST;
+      p += x;
+    }
+  else if (modspec == '~')
+    {
+      x = p && p[0] == modspec;
+      modop = x ? CASE_TOGGLEALL : CASE_TOGGLE;
+      p += x;
+    }
+    
+  lpat = p ? savestring (p) : 0;
+  /* Perform the same expansions on the pattern as performed by the
+     pattern removal expansions.  FOR LATER */
+  pat = lpat ? getpattern (lpat, quoted, 1) : 0;
+
+  /* OK, now we do the case modification. */
+  switch (vtype)
+    {
+    case VT_VARIABLE:
+    case VT_ARRAYMEMBER:
+      temp = sh_modcase (val, pat, modop);
+      if (vtype == VT_VARIABLE)
+       FREE (val);
+      if (temp)
+       {
+         tt = (mflags & MATCH_QUOTED) ? quote_string (temp) : quote_escapes (temp);
+         free (temp);
+         temp = tt;
+       }
+      break;
+
+    case VT_POSPARMS:
+      temp = pos_params_modcase (val, pat, modop, mflags);
+      if (temp && (mflags & MATCH_QUOTED)  == 0)
+       {
+         tt = quote_escapes (temp);
+         free (temp);
+         temp = tt;
+       }
+      break;
+
+#if defined (ARRAY_VARS)
+    case VT_ARRAYVAR:
+      temp = assoc_p (v) ? assoc_modcase (assoc_cell (v), pat, modop, mflags)
+                        : array_modcase (array_cell (v), pat, modop, mflags);
+      /* Don't call quote_escapes; array_modcase calls array_quote_escapes
+        as appropriate before adding the space separators; ditto for
+        assoc_modcase. */
+      break;
+#endif
+    }
+
+  FREE (pat);
+  free (lpat);
+
+  return temp;
+}
+
+/* Check for unbalanced parens in S, which is the contents of $(( ... )).  If
+   any occur, this must be a nested command substitution, so return 0.
+   Otherwise, return 1.  A valid arithmetic expression must always have a
+   ( before a matching ), so any cases where there are more right parens
+   means that this must not be an arithmetic expression, though the parser
+   will not accept it without a balanced total number of parens. */
+static int
+chk_arithsub (s, len)
+     const char *s;
+     int len;
+{
+  int i, count;
+  DECLARE_MBSTATE;
+
+  i = count = 0;
+  while (i < len)
+    {
+      if (s[i] == LPAREN)
+       count++;
+      else if (s[i] == RPAREN)
+       {
+         count--;
+         if (count < 0)
+           return 0;
+       }
+
+      switch (s[i])
+       {
+       default:
+         ADVANCE_CHAR (s, len, i);
+         break;
+
+       case '\\':
+         i++;
+         if (s[i])
+           ADVANCE_CHAR (s, len, i);
+         break;
+
+       case '\'':
+         i = skip_single_quoted (s, len, ++i);
+         break;
+
+       case '"':
+         i = skip_double_quoted ((char *)s, len, ++i);
+         break;
+       }
+    }
+
+  return (count == 0);
+}
+
+/****************************************************************/
+/*                                                             */
+/*     Functions to perform parameter expansion on a string    */
+/*                                                             */
+/****************************************************************/
+
+/* ${[#][!]name[[:][^[^]][,[,]]#[#]%[%]-=?+[word][:e1[:e2]]]} */
+static WORD_DESC *
+parameter_brace_expand (string, indexp, quoted, pflags, quoted_dollar_atp, contains_dollar_at)
+     char *string;
+     int *indexp, quoted, *quoted_dollar_atp, *contains_dollar_at, pflags;
+{
+  int check_nullness, var_is_set, var_is_null, var_is_special;
+  int want_substring, want_indir, want_patsub, want_casemod;
+  char *name, *value, *temp, *temp1;
+  WORD_DESC *tdesc, *ret;
+  int t_index, sindex, c, tflag, modspec;
+  intmax_t number;
+  arrayind_t ind;
+
+  temp = temp1 = value = (char *)NULL;
+  var_is_set = var_is_null = var_is_special = check_nullness = 0;
+  want_substring = want_indir = want_patsub = want_casemod = 0;
+
+  sindex = *indexp;
+  t_index = ++sindex;
+  /* ${#var} doesn't have any of the other parameter expansions on it. */
+  if (string[t_index] == '#' && legal_variable_starter (string[t_index+1]))            /* {{ */
+    name = string_extract (string, &t_index, "}", SX_VARNAME);
+  else
+#if defined (CASEMOD_EXPANSIONS)
+    /* To enable case-toggling expansions using the `~' operator character
+       change the 1 to 0. */
+#  if defined (CASEMOD_CAPCASE)
+    name = string_extract (string, &t_index, "#%^,~:-=?+/}", SX_VARNAME);
+#  else
+    name = string_extract (string, &t_index, "#%^,:-=?+/}", SX_VARNAME);
+#  endif /* CASEMOD_CAPCASE */
+#else
+    name = string_extract (string, &t_index, "#%:-=?+/}", SX_VARNAME);
+#endif /* CASEMOD_EXPANSIONS */
+
+  ret = 0;
+  tflag = 0;
+
+  ind = INTMAX_MIN;
+
+  /* If the name really consists of a special variable, then make sure
+     that we have the entire name.  We don't allow indirect references
+     to special variables except `#', `?', `@' and `*'. */
+  if ((sindex == t_index && VALID_SPECIAL_LENGTH_PARAM (string[t_index])) ||
+      (sindex == t_index - 1 && string[sindex] == '!' && VALID_INDIR_PARAM (string[t_index])))
+    {
+      t_index++;
+      free (name);
+      temp1 = string_extract (string, &t_index, "#%:-=?+/}", 0);
+      name = (char *)xmalloc (3 + (strlen (temp1)));
+      *name = string[sindex];
+      if (string[sindex] == '!')
+       {
+         /* indirect reference of $#, $?, $@, or $* */
+         name[1] = string[sindex + 1];
+         strcpy (name + 2, temp1);
+       }
+      else     
+       strcpy (name + 1, temp1);
+      free (temp1);
+    }
+  sindex = t_index;
+
+  /* Find out what character ended the variable name.  Then
+     do the appropriate thing. */
+  if (c = string[sindex])
+    sindex++;
+
+  /* If c is followed by one of the valid parameter expansion
+     characters, move past it as normal.  If not, assume that
+     a substring specification is being given, and do not move
+     past it. */
+  if (c == ':' && VALID_PARAM_EXPAND_CHAR (string[sindex]))
+    {
+      check_nullness++;
+      if (c = string[sindex])
+       sindex++;
+    }
+  else if (c == ':' && string[sindex] != RBRACE)
+    want_substring = 1;
+  else if (c == '/' && string[sindex] != RBRACE)
+    want_patsub = 1;
+#if defined (CASEMOD_EXPANSIONS)
+  else if (c == '^' || c == ',' || c == '~')
+    {
+      modspec = c;
+      want_casemod = 1;
+    }
+#endif
+
+  /* Catch the valid and invalid brace expressions that made it through the
+     tests above. */
+  /* ${#-} is a valid expansion and means to take the length of $-.
+     Similarly for ${#?} and ${##}... */
+  if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 &&
+       VALID_SPECIAL_LENGTH_PARAM (c) && string[sindex] == RBRACE)
+    {
+      name = (char *)xrealloc (name, 3);
+      name[1] = c;
+      name[2] = '\0';
+      c = string[sindex++];
+    }
+
+  /* ...but ${#%}, ${#:}, ${#=}, ${#+}, and ${#/} are errors. */
+  if (name[0] == '#' && name[1] == '\0' && check_nullness == 0 &&
+       member (c, "%:=+/") && string[sindex] == RBRACE)
+    {
+      temp = (char *)NULL;
+      goto bad_substitution;
+    }
+
+  /* Indirect expansion begins with a `!'.  A valid indirect expansion is
+     either a variable name, one of the positional parameters or a special
+     variable that expands to one of the positional parameters. */
+  want_indir = *name == '!' &&
+    (legal_variable_starter ((unsigned char)name[1]) || DIGIT (name[1])
+                                       || VALID_INDIR_PARAM (name[1]));
+
+  /* Determine the value of this variable. */
+
+  /* Check for special variables, directly referenced. */
+  if (SPECIAL_VAR (name, want_indir))
+    var_is_special++;
+
+  /* Check for special expansion things, like the length of a parameter */
+  if (*name == '#' && name[1])
+    {
+      /* If we are not pointing at the character just after the
+        closing brace, then we haven't gotten all of the name.
+        Since it begins with a special character, this is a bad
+        substitution.  Also check NAME for validity before trying
+        to go on. */
+      if (string[sindex - 1] != RBRACE || (valid_length_expression (name) == 0))
+       {
+         temp = (char *)NULL;
+         goto bad_substitution;
+       }
+
+      number = parameter_brace_expand_length (name);
+      if (number == INTMAX_MIN && unbound_vars_is_error)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name+1);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+      free (name);
+
+      *indexp = sindex;
+      if (number < 0)
+        return (&expand_wdesc_error);
+      else
+       {
+         ret = alloc_word_desc ();
+         ret->word = itos (number);
+         return ret;
+       }
+    }
+
+  /* ${@} is identical to $@. */
+  if (name[0] == '@' && name[1] == '\0')
+    {
+      if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+       *quoted_dollar_atp = 1;
+
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+    }
+
+  /* Process ${!PREFIX*} expansion. */
+  if (want_indir && string[sindex - 1] == RBRACE &&
+      (string[sindex - 2] == '*' || string[sindex - 2] == '@') &&
+      legal_variable_starter ((unsigned char) name[1]))
+    {
+      char **x;
+      WORD_LIST *xlist;
+
+      temp1 = savestring (name + 1);
+      number = strlen (temp1);
+      temp1[number - 1] = '\0';
+      x = all_variables_matching_prefix (temp1);
+      xlist = strvec_to_word_list (x, 0, 0);
+      if (string[sindex - 2] == '*')
+       temp = string_list_dollar_star (xlist);
+      else
+       {
+         temp = string_list_dollar_at (xlist, quoted);
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+           *quoted_dollar_atp = 1;
+         if (contains_dollar_at)
+           *contains_dollar_at = 1;
+       }
+      free (x);
+      dispose_words (xlist);
+      free (temp1);
+      *indexp = sindex;
+
+      ret = alloc_word_desc ();
+      ret->word = temp;
+      return ret;
+    }
+
+#if defined (ARRAY_VARS)      
+  /* Process ${!ARRAY[@]} and ${!ARRAY[*]} expansion. */ /* [ */
+  if (want_indir && string[sindex - 1] == RBRACE &&
+      string[sindex - 2] == ']' && valid_array_reference (name+1))
+    {
+      char *x, *x1;
+
+      temp1 = savestring (name + 1);
+      x = array_variable_name (temp1, &x1, (int *)0);  /* [ */
+      FREE (x);
+      if (ALL_ELEMENT_SUB (x1[0]) && x1[1] == ']')
+       {
+         temp = array_keys (temp1, quoted);    /* handles assoc vars too */
+         if (x1[0] == '@')
+           {
+             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+               *quoted_dollar_atp = 1;
+             if (contains_dollar_at)
+               *contains_dollar_at = 1;
+           }       
+
+         free (temp1);
+         *indexp = sindex;
+
+         ret = alloc_word_desc ();
+         ret->word = temp;
+         return ret;
+       }
+
+      free (temp1);
+    }
+#endif /* ARRAY_VARS */
+      
+  /* Make sure that NAME is valid before trying to go on. */
+  if (valid_brace_expansion_word (want_indir ? name + 1 : name,
+                                       var_is_special) == 0)
+    {
+      temp = (char *)NULL;
+      goto bad_substitution;
+    }
+
+  if (want_indir)
+    tdesc = parameter_brace_expand_indir (name + 1, var_is_special, quoted, quoted_dollar_atp, contains_dollar_at);
+  else
+    tdesc = parameter_brace_expand_word (name, var_is_special, quoted, PF_IGNUNBOUND|(pflags&PF_NOSPLIT2), &ind);
+
+  if (tdesc)
+    {
+      temp = tdesc->word;
+      tflag = tdesc->flags;
+      dispose_word_desc (tdesc);
+    }
+  else
+    temp = (char  *)0;
+
+#if defined (ARRAY_VARS)
+  if (valid_array_reference (name))
+    chk_atstar (name, quoted, quoted_dollar_atp, contains_dollar_at);
+#endif
+
+  var_is_set = temp != (char *)0;
+  var_is_null = check_nullness && (var_is_set == 0 || *temp == 0);
+
+  /* Get the rest of the stuff inside the braces. */
+  if (c && c != RBRACE)
+    {
+      /* Extract the contents of the ${ ... } expansion
+        according to the Posix.2 rules. */
+      value = extract_dollar_brace_string (string, &sindex, quoted, (c == '%' || c == '#') ? SX_POSIXEXP : 0);
+      if (string[sindex] == RBRACE)
+       sindex++;
+      else
+       goto bad_substitution;
+    }
+  else
+    value = (char *)NULL;
+
+  *indexp = sindex;
+
+  /* All the cases where an expansion can possibly generate an unbound
+     variable error. */
+  if (want_substring || want_patsub || want_casemod || c == '#' || c == '%' || c == RBRACE)
+    {
+      if (var_is_set == 0 && unbound_vars_is_error && ((name[0] != '@' && name[0] != '*') || name[1]))
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (name);
+         FREE (value);
+         FREE (temp);
+         free (name);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+    }
+    
+  /* If this is a substring spec, process it and add the result. */
+  if (want_substring)
+    {
+      temp1 = parameter_brace_substring (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      FREE (name);
+      FREE (value);
+      FREE (temp);
+
+      if (temp1 == &expand_param_error)
+       return (&expand_wdesc_error);
+      else if (temp1 == &expand_param_fatal)
+       return (&expand_wdesc_fatal);
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+    }
+  else if (want_patsub)
+    {
+      temp1 = parameter_brace_patsub (name, temp, ind, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      FREE (name);
+      FREE (value);
+      FREE (temp);
+
+      if (temp1 == &expand_param_error)
+       return (&expand_wdesc_error);
+      else if (temp1 == &expand_param_fatal)
+       return (&expand_wdesc_fatal);
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+    }
+#if defined (CASEMOD_EXPANSIONS)
+  else if (want_casemod)
+    {
+      temp1 = parameter_brace_casemod (name, temp, ind, modspec, value, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      FREE (name);
+      FREE (value);
+      FREE (temp);
+
+      if (temp1 == &expand_param_error)
+       return (&expand_wdesc_error);
+      else if (temp1 == &expand_param_fatal)
+       return (&expand_wdesc_fatal);
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+    }
+#endif
+
+  /* Do the right thing based on which character ended the variable name. */
+  switch (c)
+    {
+    default:
+    case '\0':
+    bad_substitution:
+      report_error (_("%s: bad substitution"), string ? string : "??");
+      FREE (value);
+      FREE (temp);
+      free (name);
+      return &expand_wdesc_error;
+
+    case RBRACE:
+      break;
+
+    case '#':  /* ${param#[#]pattern} */
+    case '%':  /* ${param%[%]pattern} */
+      if (value == 0 || *value == '\0' || temp == 0 || *temp == '\0')
+       {
+         FREE (value);
+         break;
+       }
+      temp1 = parameter_brace_remove_pattern (name, temp, ind, value, c, quoted, (tflag & W_ARRAYIND) ? AV_USEIND : 0);
+      free (temp);
+      free (value);
+      free (name);
+
+      ret = alloc_word_desc ();
+      ret->word = temp1;
+      if (temp1 && QUOTED_NULL (temp1) && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       ret->flags |= W_QUOTED|W_HASQUOTEDNULL;
+      return ret;
+
+    case '-':
+    case '=':
+    case '?':
+    case '+':
+      if (var_is_set && var_is_null == 0)
+       {
+         /* If the operator is `+', we don't want the value of the named
+            variable for anything, just the value of the right hand side. */
+         if (c == '+')
+           {
+             /* XXX -- if we're double-quoted and the named variable is "$@",
+                       we want to turn off any special handling of "$@" --
+                       we're not using it, so whatever is on the rhs applies. */
+             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+               *quoted_dollar_atp = 0;
+             if (contains_dollar_at)
+               *contains_dollar_at = 0;
+
+             FREE (temp);
+             if (value)
+               {
+                 /* From Posix discussion on austin-group list.  Issue 221
+                    requires that backslashes escaping `}' inside
+                    double-quoted ${...} be removed. */
+                 if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+                   quoted |= Q_DOLBRACE;
+                 ret = parameter_brace_expand_rhs (name, value, c,
+                                                   quoted,
+                                                   quoted_dollar_atp,
+                                                   contains_dollar_at);
+                 /* XXX - fix up later, esp. noting presence of
+                          W_HASQUOTEDNULL in ret->flags */
+                 free (value);
+               }
+             else
+               temp = (char *)NULL;
+           }
+         else
+           {
+             FREE (value);
+           }
+         /* Otherwise do nothing; just use the value in TEMP. */
+       }
+      else     /* VAR not set or VAR is NULL. */
+       {
+         FREE (temp);
+         temp = (char *)NULL;
+         if (c == '=' && var_is_special)
+           {
+             report_error (_("$%s: cannot assign in this way"), name);
+             free (name);
+             free (value);
+             return &expand_wdesc_error;
+           }
+         else if (c == '?')
+           {
+             parameter_brace_expand_error (name, value);
+             return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+           }
+         else if (c != '+')
+           {
+             /* XXX -- if we're double-quoted and the named variable is "$@",
+                       we want to turn off any special handling of "$@" --
+                       we're not using it, so whatever is on the rhs applies. */
+             if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && quoted_dollar_atp)
+               *quoted_dollar_atp = 0;
+             if (contains_dollar_at)
+               *contains_dollar_at = 0;
+
+             /* From Posix discussion on austin-group list.  Issue 221 requires
+                that backslashes escaping `}' inside double-quoted ${...} be
+                removed. */
+             if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+               quoted |= Q_DOLBRACE;
+             ret = parameter_brace_expand_rhs (name, value, c, quoted,
+                                               quoted_dollar_atp,
+                                               contains_dollar_at);
+             /* XXX - fix up later, esp. noting presence of
+                      W_HASQUOTEDNULL in tdesc->flags */
+           }
+         free (value);
+       }
+
+      break;
+    }
+  free (name);
+
+  if (ret == 0)
+    {
+      ret = alloc_word_desc ();
+      ret->flags = tflag;
+      ret->word = temp;
+    }
+  return (ret);
+}
+
+/* Expand a single ${xxx} expansion.  The braces are optional.  When
+   the braces are used, parameter_brace_expand() does the work,
+   possibly calling param_expand recursively. */
+static WORD_DESC *
+param_expand (string, sindex, quoted, expanded_something,
+             contains_dollar_at, quoted_dollar_at_p, had_quoted_null_p,
+             pflags)
+     char *string;
+     int *sindex, quoted, *expanded_something, *contains_dollar_at;
+     int *quoted_dollar_at_p, *had_quoted_null_p, pflags;
+{
+  char *temp, *temp1, uerror[3];
+  int zindex, t_index, expok;
+  unsigned char c;
+  intmax_t number;
+  SHELL_VAR *var;
+  WORD_LIST *list;
+  WORD_DESC *tdesc, *ret;
+  int tflag;
+
+  zindex = *sindex;
+  c = string[++zindex];
+
+  temp = (char *)NULL;
+  ret = tdesc = (WORD_DESC *)NULL;
+  tflag = 0;
+
+  /* Do simple cases first. Switch on what follows '$'. */
+  switch (c)
+    {
+    /* $0 .. $9? */
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+      temp1 = dollar_vars[TODIGIT (c)];
+      if (unbound_vars_is_error && temp1 == (char *)NULL)
+       {
+         uerror[0] = '$';
+         uerror[1] = c;
+         uerror[2] = '\0';
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (uerror);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+      if (temp1)
+       temp = (*temp1 && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+                 ? quote_string (temp1)
+                 : quote_escapes (temp1);
+      else
+       temp = (char *)NULL;
+
+      break;
+
+    /* $$ -- pid of the invoking shell. */
+    case '$':
+      temp = itos (dollar_dollar_pid);
+      break;
+
+    /* $# -- number of positional parameters. */
+    case '#':
+      temp = itos (number_of_args ());
+      break;
+
+    /* $? -- return value of the last synchronous command. */
+    case '?':
+      temp = itos (last_command_exit_value);
+      break;
+
+    /* $- -- flags supplied to the shell on invocation or by `set'. */
+    case '-':
+      temp = which_set_flags ();
+      break;
+
+      /* $! -- Pid of the last asynchronous command. */
+    case '!':
+      /* If no asynchronous pids have been created, expand to nothing.
+        If `set -u' has been executed, and no async processes have
+        been created, this is an expansion error. */
+      if (last_asynchronous_pid == NO_PID)
+       {
+         if (expanded_something)
+           *expanded_something = 0;
+         temp = (char *)NULL;
+         if (unbound_vars_is_error)
+           {
+             uerror[0] = '$';
+             uerror[1] = c;
+             uerror[2] = '\0';
+             last_command_exit_value = EXECUTION_FAILURE;
+             err_unboundvar (uerror);
+             return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+           }
+       }
+      else
+       temp = itos (last_asynchronous_pid);
+      break;
+
+    /* The only difference between this and $@ is when the arg is quoted. */
+    case '*':          /* `$*' */
+      list = list_rest_of_args ();
+
+#if 0
+      /* According to austin-group posix proposal by Geoff Clare in
+        <20090505091501.GA10097@squonk.masqnet> of 5 May 2009:
+
+       "The shell shall write a message to standard error and
+        immediately exit when it tries to expand an unset parameter
+        other than the '@' and '*' special parameters."
+      */
+
+      if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0)
+       {
+         uerror[0] = '$';
+         uerror[1] = '*';
+         uerror[2] = '\0';
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (uerror);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+#endif
+
+      /* If there are no command-line arguments, this should just
+        disappear if there are other characters in the expansion,
+        even if it's quoted. */
+      if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && list == 0)
+       temp = (char *)NULL;
+      else if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
+       {
+         /* If we have "$*" we want to make a string of the positional
+            parameters, separated by the first character of $IFS, and
+            quote the whole string, including the separators.  If IFS
+            is unset, the parameters are separated by ' '; if $IFS is
+            null, the parameters are concatenated. */
+         temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list);
+         temp1 = quote_string (temp);
+         if (*temp == 0)
+           tflag |= W_HASQUOTEDNULL;
+         free (temp);
+         temp = temp1;
+       }
+      else
+       {
+         /* We check whether or not we're eventually going to split $* here,
+            for example when IFS is empty and we are processing the rhs of
+            an assignment statement.  In that case, we don't separate the
+            arguments at all.  Otherwise, if the $* is not quoted it is
+            identical to $@ */
+#if 1
+#  if defined (HANDLE_MULTIBYTE)
+         if (expand_no_split_dollar_star && ifs_firstc[0] == 0)
+#  else
+         if (expand_no_split_dollar_star && ifs_firstc == 0)
+#  endif
+           temp = string_list_dollar_star (list);
+         else
+           temp = string_list_dollar_at (list, quoted);
+#else
+         temp = string_list_dollar_at (list, quoted);
+#endif
+         if (expand_no_split_dollar_star == 0 && contains_dollar_at)
+           *contains_dollar_at = 1;
+       }
+
+      dispose_words (list);
+      break;
+
+    /* When we have "$@" what we want is "$1" "$2" "$3" ... This
+       means that we have to turn quoting off after we split into
+       the individually quoted arguments so that the final split
+       on the first character of $IFS is still done.  */
+    case '@':          /* `$@' */
+      list = list_rest_of_args ();
+
+#if 0
+      /* According to austin-group posix proposal by Geoff Clare in
+        <20090505091501.GA10097@squonk.masqnet> of 5 May 2009:
+
+       "The shell shall write a message to standard error and
+        immediately exit when it tries to expand an unset parameter
+        other than the '@' and '*' special parameters."
+      */
+
+      if (list == 0 && unbound_vars_is_error && (pflags & PF_IGNUNBOUND) == 0)
+       {
+         uerror[0] = '$';
+         uerror[1] = '@';
+         uerror[2] = '\0';
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (uerror);
+         return (interactive_shell ? &expand_wdesc_error : &expand_wdesc_fatal);
+       }
+#endif
+
+      /* We want to flag the fact that we saw this.  We can't turn
+        off quoting entirely, because other characters in the
+        string might need it (consider "\"$@\""), but we need some
+        way to signal that the final split on the first character
+        of $IFS should be done, even though QUOTED is 1. */
+      /* XXX - should this test include Q_PATQUOTE? */
+      if (quoted_dollar_at_p && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+       *quoted_dollar_at_p = 1;
+      if (contains_dollar_at)
+       *contains_dollar_at = 1;
+
+#if 0
+      if (pflags & PF_NOSPLIT2)
+       temp = string_list_internal (quoted ? quote_list (list) : list, " ");
+      else
+#endif
+      /* We want to separate the positional parameters with the first
+        character of $IFS in case $IFS is something other than a space.
+        We also want to make sure that splitting is done no matter what --
+        according to POSIX.2, this expands to a list of the positional
+        parameters no matter what IFS is set to. */
+      temp = string_list_dollar_at (list, quoted);
+
+      dispose_words (list);
+      break;
+
+    case LBRACE:
+      tdesc = parameter_brace_expand (string, &zindex, quoted, pflags,
+                                     quoted_dollar_at_p,
+                                     contains_dollar_at);
+
+      if (tdesc == &expand_wdesc_error || tdesc == &expand_wdesc_fatal)
+       return (tdesc);
+      temp = tdesc ? tdesc->word : (char *)0;
+
+      /* XXX */
+      /* Quoted nulls should be removed if there is anything else
+        in the string. */
+      /* Note that we saw the quoted null so we can add one back at
+        the end of this function if there are no other characters
+        in the string, discard TEMP, and go on.  The exception to
+        this is when we have "${@}" and $1 is '', since $@ needs
+        special handling. */
+      if (tdesc && tdesc->word && (tdesc->flags & W_HASQUOTEDNULL) && QUOTED_NULL (temp))
+       {
+         if (had_quoted_null_p)
+           *had_quoted_null_p = 1;
+         if (*quoted_dollar_at_p == 0)
+           {
+             free (temp);
+             tdesc->word = temp = (char *)NULL;
+           }
+           
+       }
+
+      ret = tdesc;
+      goto return0;
+
+    /* Do command or arithmetic substitution. */
+    case LPAREN:
+      /* We have to extract the contents of this paren substitution. */
+      t_index = zindex + 1;
+      temp = extract_command_subst (string, &t_index, 0);
+      zindex = t_index;
+
+      /* For Posix.2-style `$(( ))' arithmetic substitution,
+        extract the expression and pass it to the evaluator. */
+      if (temp && *temp == LPAREN)
+       {
+         char *temp2;
+         temp1 = temp + 1;
+         temp2 = savestring (temp1);
+         t_index = strlen (temp2) - 1;
+
+         if (temp2[t_index] != RPAREN)
+           {
+             free (temp2);
+             goto comsub;
+           }
+
+         /* Cut off ending `)' */
+         temp2[t_index] = '\0';
+
+         if (chk_arithsub (temp2, t_index) == 0)
+           {
+             free (temp2);
+#if 0
+             internal_warning (_("future versions of the shell will force evaluation as an arithmetic substitution"));
+#endif
+             goto comsub;
+           }
+
+         /* Expand variables found inside the expression. */
+         temp1 = expand_arith_string (temp2, Q_DOUBLE_QUOTES);
+         free (temp2);
+
+arithsub:
+         /* No error messages. */
+         this_command_name = (char *)NULL;
+         number = evalexp (temp1, &expok);
+         free (temp);
+         free (temp1);
+         if (expok == 0)
+           {
+             if (interactive_shell == 0 && posixly_correct)
+               {
+                 last_command_exit_value = EXECUTION_FAILURE;
+                 return (&expand_wdesc_fatal);
+               }
+             else
+               return (&expand_wdesc_error);
+           }
+         temp = itos (number);
+         break;
+       }
+
+comsub:
+      if (pflags & PF_NOCOMSUB)
+       /* we need zindex+1 because string[zindex] == RPAREN */
+       temp1 = substring (string, *sindex, zindex+1);
+      else
+       {
+         tdesc = command_substitute (temp, quoted);
+         temp1 = tdesc ? tdesc->word : (char *)NULL;
+         if (tdesc)
+           dispose_word_desc (tdesc);
+       }
+      FREE (temp);
+      temp = temp1;
+      break;
+
+    /* Do POSIX.2d9-style arithmetic substitution.  This will probably go
+       away in a future bash release. */
+    case '[':
+      /* Extract the contents of this arithmetic substitution. */
+      t_index = zindex + 1;
+      temp = extract_arithmetic_subst (string, &t_index);
+      zindex = t_index;
+      if (temp == 0)
+       {
+         temp = savestring (string);
+         if (expanded_something)
+           *expanded_something = 0;
+         goto return0;
+       }         
+
+       /* Do initial variable expansion. */
+      temp1 = expand_arith_string (temp, Q_DOUBLE_QUOTES);
+
+      goto arithsub;
+
+    default:
+      /* Find the variable in VARIABLE_LIST. */
+      temp = (char *)NULL;
+
+      for (t_index = zindex; (c = string[zindex]) && legal_variable_char (c); zindex++)
+       ;
+      temp1 = (zindex > t_index) ? substring (string, t_index, zindex) : (char *)NULL;
+
+      /* If this isn't a variable name, then just output the `$'. */
+      if (temp1 == 0 || *temp1 == '\0')
+       {
+         FREE (temp1);
+         temp = (char *)xmalloc (2);
+         temp[0] = '$';
+         temp[1] = '\0';
+         if (expanded_something)
+           *expanded_something = 0;
+         goto return0;
+       }
+
+      /* If the variable exists, return its value cell. */
+      var = find_variable (temp1);
+
+      if (var && invisible_p (var) == 0 && var_isset (var))
+       {
+#if defined (ARRAY_VARS)
+         if (assoc_p (var) || array_p (var))
+           {
+             temp = array_p (var) ? array_reference (array_cell (var), 0)
+                                  : assoc_reference (assoc_cell (var), "0");
+             if (temp)
+               temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+                         ? quote_string (temp)
+                         : quote_escapes (temp);
+             else if (unbound_vars_is_error)
+               goto unbound_variable;
+           }
+         else
+#endif
+           {
+             temp = value_cell (var);
+
+             temp = (*temp && (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)))
+                       ? quote_string (temp)
+                       : quote_escapes (temp);
+           }
+
+         free (temp1);
+
+         goto return0;
+       }
+
+      temp = (char *)NULL;
+
+unbound_variable:
+      if (unbound_vars_is_error)
+       {
+         last_command_exit_value = EXECUTION_FAILURE;
+         err_unboundvar (temp1);
+       }
+      else
+       {
+         free (temp1);
+         goto return0;
+       }
+
+      free (temp1);
+      last_command_exit_value = EXECUTION_FAILURE;
+      return ((unbound_vars_is_error && interactive_shell == 0)
+               ? &expand_wdesc_fatal
+               : &expand_wdesc_error);
+    }
+
+  if (string[zindex])
+    zindex++;
+
+return0:
+  *sindex = zindex;
+
+  if (ret == 0)
+    {
+      ret = alloc_word_desc ();
+      ret->flags = tflag;      /* XXX */
+      ret->word = temp;
+    }
+  return ret;
+}
+
+/* Make a word list which is the result of parameter and variable
+   expansion, command substitution, arithmetic substitution, and
+   quote removal of WORD.  Return a pointer to a WORD_LIST which is
+   the result of the expansion.  If WORD contains a null word, the
+   word list returned is also null.
+
+   QUOTED contains flag values defined in shell.h.
+
+   ISEXP is used to tell expand_word_internal that the word should be
+   treated as the result of an expansion.  This has implications for
+   how IFS characters in the word are treated.
+
+   CONTAINS_DOLLAR_AT and EXPANDED_SOMETHING are return values; when non-null
+   they point to an integer value which receives information about expansion.
+   CONTAINS_DOLLAR_AT gets non-zero if WORD contained "$@", else zero.
+   EXPANDED_SOMETHING get non-zero if WORD contained any parameter expansions,
+   else zero.
+
+   This only does word splitting in the case of $@ expansion.  In that
+   case, we split on ' '. */
+
+/* Values for the local variable quoted_state. */
+#define UNQUOTED        0
+#define PARTIALLY_QUOTED 1
+#define WHOLLY_QUOTED    2
+
+static WORD_LIST *
+expand_word_internal (word, quoted, isexp, contains_dollar_at, expanded_something)
+     WORD_DESC *word;
+     int quoted, isexp;
+     int *contains_dollar_at;
+     int *expanded_something;
+{
+  WORD_LIST *list;
+  WORD_DESC *tword;
+
+  /* The intermediate string that we build while expanding. */
+  char *istring;
+
+  /* The current size of the above object. */
+  int istring_size;
+
+  /* Index into ISTRING. */
+  int istring_index;
+
+  /* Temporary string storage. */
+  char *temp, *temp1;
+
+  /* The text of WORD. */
+  register char *string;
+
+  /* The size of STRING. */
+  size_t string_size;
+
+  /* The index into STRING. */
+  int sindex;
+
+  /* This gets 1 if we see a $@ while quoted. */
+  int quoted_dollar_at;
+
+  /* One of UNQUOTED, PARTIALLY_QUOTED, or WHOLLY_QUOTED, depending on
+     whether WORD contains no quoting characters, a partially quoted
+     string (e.g., "xx"ab), or is fully quoted (e.g., "xxab"). */
+  int quoted_state;
+
+  /* State flags */
+  int had_quoted_null;
+  int has_dollar_at;
+  int tflag;
+  int pflags;                  /* flags passed to param_expand */
+
+  int assignoff;               /* If assignment, offset of `=' */
+
+  register unsigned char c;    /* Current character. */
+  int t_index;                 /* For calls to string_extract_xxx. */
+
+  char twochars[2];
+
+  DECLARE_MBSTATE;
+
+  istring = (char *)xmalloc (istring_size = DEFAULT_INITIAL_ARRAY_SIZE);
+  istring[istring_index = 0] = '\0';
+  quoted_dollar_at = had_quoted_null = has_dollar_at = 0;
+  quoted_state = UNQUOTED;
+
+  string = word->word;
+  if (string == 0)
+    goto finished_with_string;
+  /* Don't need the string length for the SADD... and COPY_ macros unless
+     multibyte characters are possible. */
+  string_size = (MB_CUR_MAX > 1) ? strlen (string) : 1;
+
+  if (contains_dollar_at)
+    *contains_dollar_at = 0;
+
+  assignoff = -1;
+
+  /* Begin the expansion. */
+
+  for (sindex = 0; ;)
+    {
+      c = string[sindex];
+
+      /* Case on toplevel character. */
+      switch (c)
+       {
+       case '\0':
+         goto finished_with_string;
+
+       case CTLESC:
+         sindex++;
+#if HANDLE_MULTIBYTE
+         if (MB_CUR_MAX > 1 && string[sindex])
+           {
+             SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
+           }
+         else
+#endif
+           {
+             temp = (char *)xmalloc (3);
+             temp[0] = CTLESC;
+             temp[1] = c = string[sindex];
+             temp[2] = '\0';
+           }
+
+dollar_add_string:
+         if (string[sindex])
+           sindex++;
+
+add_string:
+         if (temp)
+           {
+             istring = sub_append_string (temp, istring, &istring_index, &istring_size);
+             temp = (char *)0;
+           }
+
+         break;
+
+#if defined (PROCESS_SUBSTITUTION)
+         /* Process substitution. */
+       case '<':
+       case '>':
+         {
+           if (string[++sindex] != LPAREN || (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (word->flags & (W_DQUOTE|W_NOPROCSUB)) || posixly_correct)
+             {
+               sindex--;       /* add_character: label increments sindex */
+               goto add_character;
+             }
+           else
+             t_index = sindex + 1; /* skip past both '<' and LPAREN */
+
+           temp1 = extract_process_subst (string, (c == '<') ? "<(" : ">(", &t_index); /*))*/
+           sindex = t_index;
+
+           /* If the process substitution specification is `<()', we want to
+              open the pipe for writing in the child and produce output; if
+              it is `>()', we want to open the pipe for reading in the child
+              and consume input. */
+           temp = temp1 ? process_substitute (temp1, (c == '>')) : (char *)0;
+
+           FREE (temp1);
+
+           goto dollar_add_string;
+         }
+#endif /* PROCESS_SUBSTITUTION */
+
+       case '=':
+         /* Posix.2 section 3.6.1 says that tildes following `=' in words
+            which are not assignment statements are not expanded.  If the
+            shell isn't in posix mode, though, we perform tilde expansion
+            on `likely candidate' unquoted assignment statements (flags
+            include W_ASSIGNMENT but not W_QUOTED).  A likely candidate
+            contains an unquoted :~ or =~.  Something to think about: we
+            now have a flag that says  to perform tilde expansion on arguments
+            to `assignment builtins' like declare and export that look like
+            assignment statements.  We now do tilde expansion on such words
+            even in POSIX mode. */     
+         if (word->flags & (W_ASSIGNRHS|W_NOTILDE))
+           {
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+               goto add_ifs_character;
+             else
+               goto add_character;
+           }
+         /* If we're not in posix mode or forcing assignment-statement tilde
+            expansion, note where the `=' appears in the word and prepare to
+            do tilde expansion following the first `='. */
+         if ((word->flags & W_ASSIGNMENT) &&
+             (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
+             assignoff == -1 && sindex > 0)
+           assignoff = sindex;
+         if (sindex == assignoff && string[sindex+1] == '~')   /* XXX */
+           word->flags |= W_ITILDE;
+#if 0
+         else if ((word->flags & W_ASSIGNMENT) &&
+                  (posixly_correct == 0 || (word->flags & W_TILDEEXP)) &&
+                  string[sindex+1] == '~')
+           word->flags |= W_ITILDE;
+#endif
+         if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+           goto add_ifs_character;
+         else
+           goto add_character;
+
+       case ':':
+         if (word->flags & W_NOTILDE)
+           {
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+               goto add_ifs_character;
+             else
+               goto add_character;
+           }
+
+         if ((word->flags & (W_ASSIGNMENT|W_ASSIGNRHS|W_TILDEEXP)) &&
+             string[sindex+1] == '~')
+           word->flags |= W_ITILDE;
+
+         if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c))
+           goto add_ifs_character;
+         else
+           goto add_character;
+
+       case '~':
+         /* If the word isn't supposed to be tilde expanded, or we're not
+            at the start of a word or after an unquoted : or = in an
+            assignment statement, we don't do tilde expansion. */
+         if ((word->flags & (W_NOTILDE|W_DQUOTE)) ||
+             (sindex > 0 && ((word->flags & W_ITILDE) == 0)) ||
+             (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+           {
+             word->flags &= ~W_ITILDE;
+             if (isexp == 0 && (word->flags & (W_NOSPLIT|W_NOSPLIT2)) == 0 && isifs (c) && (quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) == 0)
+               goto add_ifs_character;
+             else
+               goto add_character;
+           }
+
+         if (word->flags & W_ASSIGNRHS)
+           tflag = 2;
+         else if (word->flags & (W_ASSIGNMENT|W_TILDEEXP))
+           tflag = 1;
+         else
+           tflag = 0;
+
+         temp = bash_tilde_find_word (string + sindex, tflag, &t_index);
+           
+         word->flags &= ~W_ITILDE;
+
+         if (temp && *temp && t_index > 0)
+           {
+             temp1 = bash_tilde_expand (temp, tflag);
+             if  (temp1 && *temp1 == '~' && STREQ (temp, temp1))
+               {
+                 FREE (temp);
+                 FREE (temp1);
+                 goto add_character;           /* tilde expansion failed */
+               }
+             free (temp);
+             temp = temp1;
+             sindex += t_index;
+             goto add_quoted_string;           /* XXX was add_string */
+           }
+         else
+           {
+             FREE (temp);
+             goto add_character;
+           }
+       
+       case '$':
+         if (expanded_something)
+           *expanded_something = 1;
+
+         has_dollar_at = 0;
+         pflags = (word->flags & W_NOCOMSUB) ? PF_NOCOMSUB : 0;
+         if (word->flags & W_NOSPLIT2)
+           pflags |= PF_NOSPLIT2;
+         tword = param_expand (string, &sindex, quoted, expanded_something,
+                              &has_dollar_at, &quoted_dollar_at,
+                              &had_quoted_null, pflags);
+
+         if (tword == &expand_wdesc_error || tword == &expand_wdesc_fatal)
+           {
+             free (string);
+             free (istring);
+             return ((tword == &expand_wdesc_error) ? &expand_word_error
+                                                    : &expand_word_fatal);
+           }
+         if (contains_dollar_at && has_dollar_at)
+           *contains_dollar_at = 1;
+
+         if (tword && (tword->flags & W_HASQUOTEDNULL))
+           had_quoted_null = 1;
+
+         temp = tword->word;
+         dispose_word_desc (tword);
+
+         goto add_string;
+         break;
+
+       case '`':               /* Backquoted command substitution. */
+         {
+           t_index = sindex++;
+
+           temp = string_extract (string, &sindex, "`", SX_REQMATCH);
+           /* The test of sindex against t_index is to allow bare instances of
+              ` to pass through, for backwards compatibility. */
+           if (temp == &extract_string_error || temp == &extract_string_fatal)
+             {
+               if (sindex - 1 == t_index)
+                 {
+                   sindex = t_index;
+                   goto add_character;
+                 }
+               report_error (_("bad substitution: no closing \"`\" in %s") , string+t_index);
+               free (string);
+               free (istring);
+               return ((temp == &extract_string_error) ? &expand_word_error
+                                                       : &expand_word_fatal);
+             }
+               
+           if (expanded_something)
+             *expanded_something = 1;
+
+           if (word->flags & W_NOCOMSUB)
+             /* sindex + 1 because string[sindex] == '`' */
+             temp1 = substring (string, t_index, sindex + 1);
+           else
+             {
+               de_backslash (temp);
+               tword = command_substitute (temp, quoted);
+               temp1 = tword ? tword->word : (char *)NULL;
+               if (tword)
+                 dispose_word_desc (tword);
+             }
+           FREE (temp);
+           temp = temp1;
+           goto dollar_add_string;
+         }
+
+       case '\\':
+         if (string[sindex + 1] == '\n')
+           {
+             sindex += 2;
+             continue;
+           }
+
+         c = string[++sindex];
+
+         if (quoted & Q_HERE_DOCUMENT)
+           tflag = CBSHDOC;
+         else if (quoted & Q_DOUBLE_QUOTES)
+           tflag = CBSDQUOTE;
+         else
+           tflag = 0;
+
+         /* From Posix discussion on austin-group list:  Backslash escaping
+            a } in ${...} is removed.  Issue 0000221 */
+         if ((quoted & Q_DOLBRACE) && c == RBRACE)
+           {
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+         else if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) && ((sh_syntaxtab[c] & tflag) == 0))
+           {
+             SCOPY_CHAR_I (twochars, '\\', c, string, sindex, string_size);
+           }
+         else if (c == 0)
+           {
+             c = CTLNUL;
+             sindex--;         /* add_character: label increments sindex */
+             goto add_character;
+           }
+         else
+           {
+             SCOPY_CHAR_I (twochars, CTLESC, c, string, sindex, string_size);
+           }
+
+         sindex++;
+add_twochars:
+         /* BEFORE jumping here, we need to increment sindex if appropriate */
+         RESIZE_MALLOCED_BUFFER (istring, istring_index, 2, istring_size,
+                                 DEFAULT_ARRAY_SIZE);
+         istring[istring_index++] = twochars[0];
+         istring[istring_index++] = twochars[1];
+         istring[istring_index] = '\0';
+
+         break;
+
+       case '"':
+#if 0
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
+#else
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+#endif
+           goto add_character;
+
+         t_index = ++sindex;
+         temp = string_extract_double_quoted (string, &sindex, 0);
+
+         /* If the quotes surrounded the entire string, then the
+            whole word was quoted. */
+         quoted_state = (t_index == 1 && string[sindex] == '\0')
+                           ? WHOLLY_QUOTED
+                           : PARTIALLY_QUOTED;
+
+         if (temp && *temp)
+           {
+             tword = alloc_word_desc ();
+             tword->word = temp;
+
+             temp = (char *)NULL;
+
+             has_dollar_at = 0;
+             /* Need to get W_HASQUOTEDNULL flag through this function. */
+             list = expand_word_internal (tword, Q_DOUBLE_QUOTES, 0, &has_dollar_at, (int *)NULL);
+
+             if (list == &expand_word_error || list == &expand_word_fatal)
+               {
+                 free (istring);
+                 free (string);
+                 /* expand_word_internal has already freed temp_word->word
+                    for us because of the way it prints error messages. */
+                 tword->word = (char *)NULL;
+                 dispose_word (tword);
+                 return list;
+               }
+
+             dispose_word (tword);
+
+             /* "$@" (a double-quoted dollar-at) expands into nothing,
+                not even a NULL word, when there are no positional
+                parameters. */
+             if (list == 0 && has_dollar_at)
+               {
+                 quoted_dollar_at++;
+                 break;
+               }
+
+             /* If we get "$@", we know we have expanded something, so we
+                need to remember it for the final split on $IFS.  This is
+                a special case; it's the only case where a quoted string
+                can expand into more than one word.  It's going to come back
+                from the above call to expand_word_internal as a list with
+                a single word, in which all characters are quoted and
+                separated by blanks.  What we want to do is to turn it back
+                into a list for the next piece of code. */
+             if (list)
+               dequote_list (list);
+
+             if (list && list->word && (list->word->flags & W_HASQUOTEDNULL))
+               had_quoted_null = 1;
+
+             if (has_dollar_at)
+               {
+                 quoted_dollar_at++;
+                 if (contains_dollar_at)
+                   *contains_dollar_at = 1;
+                 if (expanded_something)
+                   *expanded_something = 1;
+               }
+           }
+         else
+           {
+             /* What we have is "".  This is a minor optimization. */
+             FREE (temp);
+             list = (WORD_LIST *)NULL;
+           }
+
+         /* The code above *might* return a list (consider the case of "$@",
+            where it returns "$1", "$2", etc.).  We can't throw away the
+            rest of the list, and we have to make sure each word gets added
+            as quoted.  We test on tresult->next:  if it is non-NULL, we
+            quote the whole list, save it to a string with string_list, and
+            add that string. We don't need to quote the results of this
+            (and it would be wrong, since that would quote the separators
+            as well), so we go directly to add_string. */
+         if (list)
+           {
+             if (list->next)
+               {
+#if 0
+                 if (quoted_dollar_at && (word->flags & W_NOSPLIT2))
+                   temp = string_list_internal (quote_list (list), " ");
+                 else
+#endif
+                 /* Testing quoted_dollar_at makes sure that "$@" is
+                    split correctly when $IFS does not contain a space. */
+                 temp = quoted_dollar_at
+                               ? string_list_dollar_at (list, Q_DOUBLE_QUOTES)
+                               : string_list (quote_list (list));
+                 dispose_words (list);
+                 goto add_string;
+               }
+             else
+               {
+                 temp = savestring (list->word->word);
+                 tflag = list->word->flags;
+                 dispose_words (list);
+
+                 /* If the string is not a quoted null string, we want
+                    to remove any embedded unquoted CTLNUL characters.
+                    We do not want to turn quoted null strings back into
+                    the empty string, though.  We do this because we
+                    want to remove any quoted nulls from expansions that
+                    contain other characters.  For example, if we have
+                    x"$*"y or "x$*y" and there are no positional parameters,
+                    the $* should expand into nothing. */
+                 /* We use the W_HASQUOTEDNULL flag to differentiate the
+                    cases:  a quoted null character as above and when
+                    CTLNUL is contained in the (non-null) expansion
+                    of some variable.  We use the had_quoted_null flag to
+                    pass the value through this function to its caller. */
+                 if ((tflag & W_HASQUOTEDNULL) && QUOTED_NULL (temp) == 0)
+                   remove_quoted_nulls (temp); /* XXX */
+               }
+           }
+         else
+           temp = (char *)NULL;
+
+         /* We do not want to add quoted nulls to strings that are only
+            partially quoted; we can throw them away. */
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
+           continue;
+
+       add_quoted_string:
+
+         if (temp)
+           {
+             temp1 = temp;
+             temp = quote_string (temp);
+             free (temp1);
+             goto add_string;
+           }
+         else
+           {
+             /* Add NULL arg. */
+             c = CTLNUL;
+             sindex--;         /* add_character: label increments sindex */
+             goto add_character;
+           }
+
+         /* break; */
+
+       case '\'':
+#if 0
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (word->flags & W_DQUOTE))
+#else
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)))
+#endif
+           goto add_character;
+
+         t_index = ++sindex;
+         temp = string_extract_single_quoted (string, &sindex);
+
+         /* If the entire STRING was surrounded by single quotes,
+            then the string is wholly quoted. */
+         quoted_state = (t_index == 1 && string[sindex] == '\0')
+                           ? WHOLLY_QUOTED
+                           : PARTIALLY_QUOTED;
+
+         /* If all we had was '', it is a null expansion. */
+         if (*temp == '\0')
+           {
+             free (temp);
+             temp = (char *)NULL;
+           }
+         else
+           remove_quoted_escapes (temp);       /* ??? */
+
+         /* We do not want to add quoted nulls to strings that are only
+            partially quoted; such nulls are discarded. */
+         if (temp == 0 && (quoted_state == PARTIALLY_QUOTED))
+           continue;
+
+         /* If we have a quoted null expansion, add a quoted NULL to istring. */
+         if (temp == 0)
+           {
+             c = CTLNUL;
+             sindex--;         /* add_character: label increments sindex */
+             goto add_character;
+           }
+         else
+           goto add_quoted_string;
+
+         /* break; */
+
+       default:
+         /* This is the fix for " $@ " */
+       add_ifs_character:
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (isexp == 0 && isifs (c)))
+           {
+             if (string[sindex])       /* from old goto dollar_add_string */
+               sindex++;
+             if (c == 0)
+               {
+                 c = CTLNUL;
+                 goto add_character;
+               }
+             else
+               {
+#if HANDLE_MULTIBYTE
+                 if (MB_CUR_MAX > 1)
+                   sindex--;
+
+                 if (MB_CUR_MAX > 1)
+                   {
+                     SADD_MBQCHAR_BODY(temp, string, sindex, string_size);
+                   }
+                 else
+#endif
+                   {
+                     twochars[0] = CTLESC;
+                     twochars[1] = c;
+                     goto add_twochars;
+                   }
+               }
+           }
+
+         SADD_MBCHAR (temp, string, sindex, string_size);
+
+       add_character:
+         RESIZE_MALLOCED_BUFFER (istring, istring_index, 1, istring_size,
+                                 DEFAULT_ARRAY_SIZE);
+         istring[istring_index++] = c;
+         istring[istring_index] = '\0';
+
+         /* Next character. */
+         sindex++;
+       }
+    }
+
+finished_with_string:
+  /* OK, we're ready to return.  If we have a quoted string, and
+     quoted_dollar_at is not set, we do no splitting at all; otherwise
+     we split on ' '.  The routines that call this will handle what to
+     do if nothing has been expanded. */
+
+  /* Partially and wholly quoted strings which expand to the empty
+     string are retained as an empty arguments.  Unquoted strings
+     which expand to the empty string are discarded.  The single
+     exception is the case of expanding "$@" when there are no
+     positional parameters.  In that case, we discard the expansion. */
+
+  /* Because of how the code that handles "" and '' in partially
+     quoted strings works, we need to make ISTRING into a QUOTED_NULL
+     if we saw quoting characters, but the expansion was empty.
+     "" and '' are tossed away before we get to this point when
+     processing partially quoted strings.  This makes "" and $xxx""
+     equivalent when xxx is unset.  We also look to see whether we
+     saw a quoted null from a ${} expansion and add one back if we
+     need to. */
+
+  /* If we expand to nothing and there were no single or double quotes
+     in the word, we throw it away.  Otherwise, we return a NULL word.
+     The single exception is for $@ surrounded by double quotes when
+     there are no positional parameters.  In that case, we also throw
+     the word away. */
+
+  if (*istring == '\0')
+    {
+      if (quoted_dollar_at == 0 && (had_quoted_null || quoted_state == PARTIALLY_QUOTED))
+       {
+         istring[0] = CTLNUL;
+         istring[1] = '\0';
+         tword = make_bare_word (istring);
+         tword->flags |= W_HASQUOTEDNULL;              /* XXX */
+         list = make_word_list (tword, (WORD_LIST *)NULL);
+         if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+           tword->flags |= W_QUOTED;
+       }
+      /* According to sh, ksh, and Posix.2, if a word expands into nothing
+        and a double-quoted "$@" appears anywhere in it, then the entire
+        word is removed. */
+      else  if (quoted_state == UNQUOTED || quoted_dollar_at)
+       list = (WORD_LIST *)NULL;
+#if 0
+      else
+       {
+         tword = make_bare_word (istring);
+         if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+           tword->flags |= W_QUOTED;
+         list = make_word_list (tword, (WORD_LIST *)NULL);
+       }
+#else
+      else
+       list = (WORD_LIST *)NULL;
+#endif
+    }
+  else if (word->flags & W_NOSPLIT)
+    {
+      tword = make_bare_word (istring);
+      if (word->flags & W_ASSIGNMENT)
+       tword->flags |= W_ASSIGNMENT;   /* XXX */
+      if (word->flags & W_COMPASSIGN)
+       tword->flags |= W_COMPASSIGN;   /* XXX */
+      if (word->flags & W_NOGLOB)
+       tword->flags |= W_NOGLOB;       /* XXX */
+      if (word->flags & W_NOEXPAND)
+       tword->flags |= W_NOEXPAND;     /* XXX */
+      if (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES))
+       tword->flags |= W_QUOTED;
+      if (had_quoted_null)
+       tword->flags |= W_HASQUOTEDNULL;
+      list = make_word_list (tword, (WORD_LIST *)NULL);
+    }
+  else
+    {
+      char *ifs_chars;
+
+      ifs_chars = (quoted_dollar_at || has_dollar_at) ? ifs_value : (char *)NULL;
+
+      /* If we have $@, we need to split the results no matter what.  If
+        IFS is unset or NULL, string_list_dollar_at has separated the
+        positional parameters with a space, so we split on space (we have
+        set ifs_chars to " \t\n" above if ifs is unset).  If IFS is set,
+        string_list_dollar_at has separated the positional parameters
+        with the first character of $IFS, so we split on $IFS. */
+      if (has_dollar_at && ifs_chars)
+       list = list_string (istring, *ifs_chars ? ifs_chars : " ", 1);
+      else
+       {
+         tword = make_bare_word (istring);
+         if ((quoted & (Q_DOUBLE_QUOTES|Q_HERE_DOCUMENT)) || (quoted_state == WHOLLY_QUOTED))
+           tword->flags |= W_QUOTED;
+         if (word->flags & W_ASSIGNMENT)
+           tword->flags |= W_ASSIGNMENT;
+         if (word->flags & W_COMPASSIGN)
+           tword->flags |= W_COMPASSIGN;
+         if (word->flags & W_NOGLOB)
+           tword->flags |= W_NOGLOB;
+         if (word->flags & W_NOEXPAND)
+           tword->flags |= W_NOEXPAND;
+         if (had_quoted_null)
+           tword->flags |= W_HASQUOTEDNULL;    /* XXX */
+         list = make_word_list (tword, (WORD_LIST *)NULL);
+       }
+    }
+
+  free (istring);
+  return (list);
+}
+
+/* **************************************************************** */
+/*                                                                 */
+/*                Functions for Quote Removal                      */
+/*                                                                 */
+/* **************************************************************** */
+
+/* Perform quote removal on STRING.  If QUOTED > 0, assume we are obeying the
+   backslash quoting rules for within double quotes or a here document. */
+char *
+string_quote_removal (string, quoted)
+     char *string;
+     int quoted;
+{
+  size_t slen;
+  char *r, *result_string, *temp, *send;
+  int sindex, tindex, dquote;
+  unsigned char c;
+  DECLARE_MBSTATE;
+
+  /* The result can be no longer than the original string. */
+  slen = strlen (string);
+  send = string + slen;
+
+  r = result_string = (char *)xmalloc (slen + 1);
+
+  for (dquote = sindex = 0; c = string[sindex];)
+    {
+      switch (c)
+       {
+       case '\\':
+         c = string[++sindex];
+         if (c == 0)
+           {
+             *r++ = '\\';
+             break;
+           }
+         if (((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote) && (sh_syntaxtab[c] & CBSDQUOTE) == 0)
+           *r++ = '\\';
+         /* FALLTHROUGH */
+
+       default:
+         SCOPY_CHAR_M (r, string, send, sindex);
+         break;
+
+       case '\'':
+         if ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || dquote)
+           {
+             *r++ = c;
+             sindex++;
+             break;
+           }
+         tindex = sindex + 1;
+         temp = string_extract_single_quoted (string, &tindex);
+         if (temp)
+           {
+             strcpy (r, temp);
+             r += strlen (r);
+             free (temp);
+           }
+         sindex = tindex;
+         break;
+
+       case '"':
+         dquote = 1 - dquote;
+         sindex++;
+         break;
+       }
+    }
+    *r = '\0';
+    return (result_string);
+}
+
+#if 0
+/* UNUSED */
+/* Perform quote removal on word WORD.  This allocates and returns a new
+   WORD_DESC *. */
+WORD_DESC *
+word_quote_removal (word, quoted)
+     WORD_DESC *word;
+     int quoted;
+{
+  WORD_DESC *w;
+  char *t;
+
+  t = string_quote_removal (word->word, quoted);
+  w = alloc_word_desc ();
+  w->word = t ? t : savestring ("");
+  return (w);
+}
+
+/* Perform quote removal on all words in LIST.  If QUOTED is non-zero,
+   the members of the list are treated as if they are surrounded by
+   double quotes.  Return a new list, or NULL if LIST is NULL. */
+WORD_LIST *
+word_list_quote_removal (list, quoted)
+     WORD_LIST *list;
+     int quoted;
+{
+  WORD_LIST *result, *t, *tresult, *e;
+
+  for (t = list, result = (WORD_LIST *)NULL; t; t = t->next)
+    {
+      tresult = make_word_list (word_quote_removal (t->word, quoted), (WORD_LIST *)NULL);
+#if 0
+      result = (WORD_LIST *) list_append (result, tresult);
+#else
+      if (result == 0)
+       result = e = tresult;
+      else
+       {
+         e->next = tresult;
+         while (e->next)
+           e = e->next;
+       }
+#endif
+    }
+  return (result);
+}
+#endif
+
+/*******************************************
+ *                                        *
+ *    Functions to perform word splitting  *
+ *                                        *
+ *******************************************/
+
+void
+setifs (v)
+     SHELL_VAR *v;
+{
+  char *t;
+  unsigned char uc;
+
+  ifs_var = v;
+  ifs_value = (v && value_cell (v)) ? value_cell (v) : " \t\n";
+
+  /* Should really merge ifs_cmap with sh_syntaxtab.  XXX - doesn't yet
+     handle multibyte chars in IFS */
+  memset (ifs_cmap, '\0', sizeof (ifs_cmap));
+  for (t = ifs_value ; t && *t; t++)
+    {
+      uc = *t;
+      ifs_cmap[uc] = 1;
+    }
+
+#if defined (HANDLE_MULTIBYTE)
+  if (ifs_value == 0)
+    {
+      ifs_firstc[0] = '\0';
+      ifs_firstc_len = 1;
+    }
+  else
+    {
+      size_t ifs_len;
+      ifs_len = strnlen (ifs_value, MB_CUR_MAX);
+      ifs_firstc_len = MBLEN (ifs_value, ifs_len);
+      if (ifs_firstc_len == 1 || ifs_firstc_len == 0 || MB_INVALIDCH (ifs_firstc_len))
+       {
+         ifs_firstc[0] = ifs_value[0];
+         ifs_firstc[1] = '\0';
+         ifs_firstc_len = 1;
+       }
+      else
+       memcpy (ifs_firstc, ifs_value, ifs_firstc_len);
+    }
+#else
+  ifs_firstc = ifs_value ? *ifs_value : 0;
+#endif
+}
+
+char *
+getifs ()
+{
+  return ifs_value;
+}
+
+/* This splits a single word into a WORD LIST on $IFS, but only if the word
+   is not quoted.  list_string () performs quote removal for us, even if we
+   don't do any splitting. */
+WORD_LIST *
+word_split (w, ifs_chars)
+     WORD_DESC *w;
+     char *ifs_chars;
+{
+  WORD_LIST *result;
+
+  if (w)
+    {
+      char *xifs;
+
+      xifs = ((w->flags & W_QUOTED) || ifs_chars == 0) ? "" : ifs_chars;
+      result = list_string (w->word, xifs, w->flags & W_QUOTED);
+    }
+  else
+    result = (WORD_LIST *)NULL;
+
+  return (result);
+}
+
+/* Perform word splitting on LIST and return the RESULT.  It is possible
+   to return (WORD_LIST *)NULL. */
+static WORD_LIST *
+word_list_split (list)
+     WORD_LIST *list;
+{
+  WORD_LIST *result, *t, *tresult, *e;
+
+  for (t = list, result = (WORD_LIST *)NULL; t; t = t->next)
+    {
+      tresult = word_split (t->word, ifs_value);
+      if (result == 0)
+        result = e = tresult;
+      else
+       {
+         e->next = tresult;
+         while (e->next)
+           e = e->next;
+       }
+    }
+  return (result);
+}
+
+/**************************************************
+ *                                               *
+ *    Functions to expand an entire WORD_LIST    *
+ *                                               *
+ **************************************************/
+
+/* Do any word-expansion-specific cleanup and jump to top_level */
+static void
+exp_jump_to_top_level (v)
+     int v;
+{
+  set_pipestatus_from_exit (last_command_exit_value);
+
+  /* Cleanup code goes here. */
+  expand_no_split_dollar_star = 0;     /* XXX */
+  expanding_redir = 0;
+  assigning_in_environment = 0;
+
+  if (parse_and_execute_level == 0)
+    top_level_cleanup ();                      /* from sig.c */
+
+  jump_to_top_level (v);
+}
+
+/* Put NLIST (which is a WORD_LIST * of only one element) at the front of
+   ELIST, and set ELIST to the new list. */
+#define PREPEND_LIST(nlist, elist) \
+       do { nlist->next = elist; elist = nlist; } while (0)
+
+/* Separate out any initial variable assignments from TLIST.  If set -k has
+   been executed, remove all assignment statements from TLIST.  Initial
+   variable assignments and other environment assignments are placed
+   on SUBST_ASSIGN_VARLIST. */
+static WORD_LIST *
+separate_out_assignments (tlist)
+     WORD_LIST *tlist;
+{
+  register WORD_LIST *vp, *lp;
+
+  if (tlist == 0)
+    return ((WORD_LIST *)NULL);
+
+  if (subst_assign_varlist)
+    dispose_words (subst_assign_varlist);      /* Clean up after previous error */
+
+  subst_assign_varlist = (WORD_LIST *)NULL;
+  vp = lp = tlist;
+
+  /* Separate out variable assignments at the start of the command.
+     Loop invariant: vp->next == lp
+     Loop postcondition:
+       lp = list of words left after assignment statements skipped
+       tlist = original list of words
+  */
+  while (lp && (lp->word->flags & W_ASSIGNMENT))
+    {
+      vp = lp;
+      lp = lp->next;
+    }
+
+  /* If lp != tlist, we have some initial assignment statements.
+     We make SUBST_ASSIGN_VARLIST point to the list of assignment
+     words and TLIST point to the remaining words.  */
+  if (lp != tlist)
+    {
+      subst_assign_varlist = tlist;
+      /* ASSERT(vp->next == lp); */
+      vp->next = (WORD_LIST *)NULL;    /* terminate variable list */
+      tlist = lp;                      /* remainder of word list */
+    }
+
+  /* vp == end of variable list */
+  /* tlist == remainder of original word list without variable assignments */
+  if (!tlist)
+    /* All the words in tlist were assignment statements */
+    return ((WORD_LIST *)NULL);
+
+  /* ASSERT(tlist != NULL); */
+  /* ASSERT((tlist->word->flags & W_ASSIGNMENT) == 0); */
+
+  /* If the -k option is in effect, we need to go through the remaining
+     words, separate out the assignment words, and place them on
+     SUBST_ASSIGN_VARLIST. */
+  if (place_keywords_in_env)
+    {
+      WORD_LIST *tp;   /* tp == running pointer into tlist */
+
+      tp = tlist;
+      lp = tlist->next;
+
+      /* Loop Invariant: tp->next == lp */
+      /* Loop postcondition: tlist == word list without assignment statements */
+      while (lp)
+       {
+         if (lp->word->flags & W_ASSIGNMENT)
+           {
+             /* Found an assignment statement, add this word to end of
+                subst_assign_varlist (vp). */
+             if (!subst_assign_varlist)
+               subst_assign_varlist = vp = lp;
+             else
+               {
+                 vp->next = lp;
+                 vp = lp;
+               }
+
+             /* Remove the word pointed to by LP from TLIST. */
+             tp->next = lp->next;
+             /* ASSERT(vp == lp); */
+             lp->next = (WORD_LIST *)NULL;
+             lp = tp->next;
+           }
+         else
+           {
+             tp = lp;
+             lp = lp->next;
+           }
+       }
+    }
+  return (tlist);
+}
+
+#define WEXP_VARASSIGN 0x001
+#define WEXP_BRACEEXP  0x002
+#define WEXP_TILDEEXP  0x004
+#define WEXP_PARAMEXP  0x008
+#define WEXP_PATHEXP   0x010
+
+/* All of the expansions, including variable assignments at the start of
+   the list. */
+#define WEXP_ALL       (WEXP_VARASSIGN|WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP)
+
+/* All of the expansions except variable assignments at the start of
+   the list. */
+#define WEXP_NOVARS    (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP|WEXP_PATHEXP)
+
+/* All of the `shell expansions': brace expansion, tilde expansion, parameter
+   expansion, command substitution, arithmetic expansion, word splitting, and
+   quote removal. */
+#define WEXP_SHELLEXP  (WEXP_BRACEEXP|WEXP_TILDEEXP|WEXP_PARAMEXP)
+
+/* Take the list of words in LIST and do the various substitutions.  Return
+   a new list of words which is the expanded list, and without things like
+   variable assignments. */
+
+WORD_LIST *
+expand_words (list)
+     WORD_LIST *list;
+{
+  return (expand_word_list_internal (list, WEXP_ALL));
+}
+
+/* Same as expand_words (), but doesn't hack variable or environment
+   variables. */
+WORD_LIST *
+expand_words_no_vars (list)
+     WORD_LIST *list;
+{
+  return (expand_word_list_internal (list, WEXP_NOVARS));
+}
+
+WORD_LIST *
+expand_words_shellexp (list)
+     WORD_LIST *list;
+{
+  return (expand_word_list_internal (list, WEXP_SHELLEXP));
+}
+
+static WORD_LIST *
+glob_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  char **glob_array, *temp_string;
+  register int glob_index;
+  WORD_LIST *glob_list, *output_list, *disposables, *next;
+  WORD_DESC *tword;
+
+  output_list = disposables = (WORD_LIST *)NULL;
+  glob_array = (char **)NULL;
+  while (tlist)
+    {
+      /* For each word, either globbing is attempted or the word is
+        added to orig_list.  If globbing succeeds, the results are
+        added to orig_list and the word (tlist) is added to the list
+        of disposable words.  If globbing fails and failed glob
+        expansions are left unchanged (the shell default), the
+        original word is added to orig_list.  If globbing fails and
+        failed glob expansions are removed, the original word is
+        added to the list of disposable words.  orig_list ends up
+        in reverse order and requires a call to REVERSE_LIST to
+        be set right.  After all words are examined, the disposable
+        words are freed. */
+      next = tlist->next;
+
+      /* If the word isn't an assignment and contains an unquoted
+        pattern matching character, then glob it. */
+      if ((tlist->word->flags & W_NOGLOB) == 0 &&
+         unquoted_glob_pattern_p (tlist->word->word))
+       {
+         glob_array = shell_glob_filename (tlist->word->word);
+
+         /* Handle error cases.
+            I don't think we should report errors like "No such file
+            or directory".  However, I would like to report errors
+            like "Read failed". */
+
+         if (glob_array == 0 || GLOB_FAILED (glob_array))
+           {
+             glob_array = (char **)xmalloc (sizeof (char *));
+             glob_array[0] = (char *)NULL;
+           }
+
+         /* Dequote the current word in case we have to use it. */
+         if (glob_array[0] == NULL)
+           {
+             temp_string = dequote_string (tlist->word->word);
+             free (tlist->word->word);
+             tlist->word->word = temp_string;
+           }
+
+         /* Make the array into a word list. */
+         glob_list = (WORD_LIST *)NULL;
+         for (glob_index = 0; glob_array[glob_index]; glob_index++)
+           {
+             tword = make_bare_word (glob_array[glob_index]);
+             tword->flags |= W_GLOBEXP;        /* XXX */
+             glob_list = make_word_list (tword, glob_list);
+           }
+
+         if (glob_list)
+           {
+             output_list = (WORD_LIST *)list_append (glob_list, output_list);
+             PREPEND_LIST (tlist, disposables);
+           }
+         else if (fail_glob_expansion != 0)
+           {
+             report_error (_("no match: %s"), tlist->word->word);
+             exp_jump_to_top_level (DISCARD);
+           }
+         else if (allow_null_glob_expansion == 0)
+           {
+             /* Failed glob expressions are left unchanged. */
+             PREPEND_LIST (tlist, output_list);
+           }
+         else
+           {
+             /* Failed glob expressions are removed. */
+             PREPEND_LIST (tlist, disposables);
+           }
+       }
+      else
+       {
+         /* Dequote the string. */
+         temp_string = dequote_string (tlist->word->word);
+         free (tlist->word->word);
+         tlist->word->word = temp_string;
+         PREPEND_LIST (tlist, output_list);
+       }
+
+      strvec_dispose (glob_array);
+      glob_array = (char **)NULL;
+
+      tlist = next;
+    }
+
+  if (disposables)
+    dispose_words (disposables);
+
+  if (output_list)
+    output_list = REVERSE_LIST (output_list, WORD_LIST *);
+
+  return (output_list);
+}
+
+#if defined (BRACE_EXPANSION)
+static WORD_LIST *
+brace_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  register char **expansions;
+  char *temp_string;
+  WORD_LIST *disposables, *output_list, *next;
+  WORD_DESC *w;
+  int eindex;
+
+  for (disposables = output_list = (WORD_LIST *)NULL; tlist; tlist = next)
+    {
+      next = tlist->next;
+
+      if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
+        {
+/*itrace("brace_expand_word_list: %s: W_COMPASSIGN|W_ASSIGNARG", tlist->word->word);*/
+         PREPEND_LIST (tlist, output_list);
+         continue;
+        }
+          
+      /* Only do brace expansion if the word has a brace character.  If
+        not, just add the word list element to BRACES and continue.  In
+        the common case, at least when running shell scripts, this will
+        degenerate to a bunch of calls to `mbschr', and then what is
+        basically a reversal of TLIST into BRACES, which is corrected
+        by a call to REVERSE_LIST () on BRACES when the end of TLIST
+        is reached. */
+      if (mbschr (tlist->word->word, LBRACE))
+       {
+         expansions = brace_expand (tlist->word->word);
+
+         for (eindex = 0; temp_string = expansions[eindex]; eindex++)
+           {
+             w = make_word (temp_string);
+             /* If brace expansion didn't change the word, preserve
+                the flags.  We may want to preserve the flags
+                unconditionally someday -- XXX */
+             if (STREQ (temp_string, tlist->word->word))
+               w->flags = tlist->word->flags;
+             output_list = make_word_list (w, output_list);
+             free (expansions[eindex]);
+           }
+         free (expansions);
+
+         /* Add TLIST to the list of words to be freed after brace
+            expansion has been performed. */
+         PREPEND_LIST (tlist, disposables);
+       }
+      else
+       PREPEND_LIST (tlist, output_list);
+    }
+
+  if (disposables)
+    dispose_words (disposables);
+
+  if (output_list)
+    output_list = REVERSE_LIST (output_list, WORD_LIST *);
+
+  return (output_list);
+}
+#endif
+
+#if defined (ARRAY_VARS)
+/* Take WORD, a compound associative array assignment, and internally run
+   'declare -A w', where W is the variable name portion of WORD. */
+static int
+make_internal_declare (word, option)
+     char *word;
+     char *option;
+{
+  int t;
+  WORD_LIST *wl;
+  WORD_DESC *w;
+
+  w = make_word (word);
+
+  t = assignment (w->word, 0);
+  w->word[t] = '\0';
+
+  wl = make_word_list (w, (WORD_LIST *)NULL);
+  wl = make_word_list (make_word (option), wl);
+
+  return (declare_builtin (wl));  
+}  
+#endif
+
+static WORD_LIST *
+shell_expand_word_list (tlist, eflags)
+     WORD_LIST *tlist;
+     int eflags;
+{
+  WORD_LIST *expanded, *orig_list, *new_list, *next, *temp_list;
+  int expanded_something, has_dollar_at;
+  char *temp_string;
+
+  /* We do tilde expansion all the time.  This is what 1003.2 says. */
+  new_list = (WORD_LIST *)NULL;
+  for (orig_list = tlist; tlist; tlist = next)
+    {
+      temp_string = tlist->word->word;
+
+      next = tlist->next;
+
+#if defined (ARRAY_VARS)
+      /* If this is a compound array assignment to a builtin that accepts
+         such assignments (e.g., `declare'), take the assignment and perform
+         it separately, handling the semantics of declarations inside shell
+         functions.  This avoids the double-evaluation of such arguments,
+         because `declare' does some evaluation of compound assignments on
+         its own. */
+      if ((tlist->word->flags & (W_COMPASSIGN|W_ASSIGNARG)) == (W_COMPASSIGN|W_ASSIGNARG))
+       {
+         int t;
+
+         if (tlist->word->flags & W_ASSIGNASSOC)
+           make_internal_declare (tlist->word->word, "-A");
+
+         t = do_word_assignment (tlist->word);
+         if (t == 0)
+           {
+             last_command_exit_value = EXECUTION_FAILURE;
+             exp_jump_to_top_level (DISCARD);
+           }
+
+         /* Now transform the word as ksh93 appears to do and go on */
+         t = assignment (tlist->word->word, 0);
+         tlist->word->word[t] = '\0';
+         tlist->word->flags &= ~(W_ASSIGNMENT|W_NOSPLIT|W_COMPASSIGN|W_ASSIGNARG|W_ASSIGNASSOC);
+       }
+#endif
+
+      expanded_something = 0;
+      expanded = expand_word_internal
+       (tlist->word, 0, 0, &has_dollar_at, &expanded_something);
+
+      if (expanded == &expand_word_error || expanded == &expand_word_fatal)
+       {
+         /* By convention, each time this error is returned,
+            tlist->word->word has already been freed. */
+         tlist->word->word = (char *)NULL;
+
+         /* Dispose our copy of the original list. */
+         dispose_words (orig_list);
+         /* Dispose the new list we're building. */
+         dispose_words (new_list);
+
+         last_command_exit_value = EXECUTION_FAILURE;
+         if (expanded == &expand_word_error)
+           exp_jump_to_top_level (DISCARD);
+         else
+           exp_jump_to_top_level (FORCE_EOF);
+       }
+
+      /* Don't split words marked W_NOSPLIT. */
+      if (expanded_something && (tlist->word->flags & W_NOSPLIT) == 0)
+       {
+         temp_list = word_list_split (expanded);
+         dispose_words (expanded);
+       }
+      else
+       {
+         /* If no parameter expansion, command substitution, process
+            substitution, or arithmetic substitution took place, then
+            do not do word splitting.  We still have to remove quoted
+            null characters from the result. */
+         word_list_remove_quoted_nulls (expanded);
+         temp_list = expanded;
+       }
+
+      expanded = REVERSE_LIST (temp_list, WORD_LIST *);
+      new_list = (WORD_LIST *)list_append (expanded, new_list);
+    }
+
+  if (orig_list)  
+    dispose_words (orig_list);
+
+  if (new_list)
+    new_list = REVERSE_LIST (new_list, WORD_LIST *);
+
+  return (new_list);
+}
+
+/* The workhorse for expand_words () and expand_words_no_vars ().
+   First arg is LIST, a WORD_LIST of words.
+   Second arg EFLAGS is a flags word controlling which expansions are
+   performed.
+
+   This does all of the substitutions: brace expansion, tilde expansion,
+   parameter expansion, command substitution, arithmetic expansion,
+   process substitution, word splitting, and pathname expansion, according
+   to the bits set in EFLAGS.  Words with the W_QUOTED or W_NOSPLIT bits
+   set, or for which no expansion is done, do not undergo word splitting.
+   Words with the W_NOGLOB bit set do not undergo pathname expansion. */
+static WORD_LIST *
+expand_word_list_internal (list, eflags)
+     WORD_LIST *list;
+     int eflags;
+{
+  WORD_LIST *new_list, *temp_list;
+  int tint;
+
+  if (list == 0)
+    return ((WORD_LIST *)NULL);
+
+  garglist = new_list = copy_word_list (list);
+  if (eflags & WEXP_VARASSIGN)
+    {
+      garglist = new_list = separate_out_assignments (new_list);
+      if (new_list == 0)
+       {
+         if (subst_assign_varlist)
+           {
+             /* All the words were variable assignments, so they are placed
+                into the shell's environment. */
+             for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
+               {
+                 this_command_name = (char *)NULL;     /* no arithmetic errors */
+                 tint = do_word_assignment (temp_list->word);
+                 /* Variable assignment errors in non-interactive shells
+                    running in Posix.2 mode cause the shell to exit. */
+                 if (tint == 0)
+                   {
+                     last_command_exit_value = EXECUTION_FAILURE;
+                     if (interactive_shell == 0 && posixly_correct)
+                       exp_jump_to_top_level (FORCE_EOF);
+                     else
+                       exp_jump_to_top_level (DISCARD);
+                   }
+               }
+             dispose_words (subst_assign_varlist);
+             subst_assign_varlist = (WORD_LIST *)NULL;
+           }
+         return ((WORD_LIST *)NULL);
+       }
+    }
+
+  /* Begin expanding the words that remain.  The expansions take place on
+     things that aren't really variable assignments. */
+
+#if defined (BRACE_EXPANSION)
+  /* Do brace expansion on this word if there are any brace characters
+     in the string. */
+  if ((eflags & WEXP_BRACEEXP) && brace_expansion && new_list)
+    new_list = brace_expand_word_list (new_list, eflags);
+#endif /* BRACE_EXPANSION */
+
+  /* Perform the `normal' shell expansions: tilde expansion, parameter and
+     variable substitution, command substitution, arithmetic expansion,
+     and word splitting. */
+  new_list = shell_expand_word_list (new_list, eflags);
+
+  /* Okay, we're almost done.  Now let's just do some filename
+     globbing. */
+  if (new_list)
+    {
+      if ((eflags & WEXP_PATHEXP) && disallow_filename_globbing == 0)
+       /* Glob expand the word list unless globbing has been disabled. */
+       new_list = glob_expand_word_list (new_list, eflags);
+      else
+       /* Dequote the words, because we're not performing globbing. */
+       new_list = dequote_list (new_list);
+    }
+
+  if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
+    {
+      sh_wassign_func_t *assign_func;
+      int is_special_builtin;
+
+      /* If the remainder of the words expand to nothing, Posix.2 requires
+        that the variable and environment assignments affect the shell's
+        environment. */
+      assign_func = new_list ? assign_in_env : do_word_assignment;
+      tempenv_assign_error = 0;
+
+      /* Posix says that special builtins exit if a variable assignment error
+        occurs in an assignment preceding it. */
+      is_special_builtin = (posixly_correct && new_list && find_special_builtin (new_list->word->word));
+      
+      for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
+       {
+         this_command_name = (char *)NULL;
+         assigning_in_environment = (assign_func == assign_in_env);
+         tint = (*assign_func) (temp_list->word);
+         assigning_in_environment = 0;
+         /* Variable assignment errors in non-interactive shells running
+            in Posix.2 mode cause the shell to exit. */
+         if (tint == 0)
+           {
+             if (assign_func == do_word_assignment)
+               {
+                 last_command_exit_value = EXECUTION_FAILURE;
+                 if (interactive_shell == 0 && posixly_correct && is_special_builtin)
+                   exp_jump_to_top_level (FORCE_EOF);
+                 else
+                   exp_jump_to_top_level (DISCARD);
+               }
+             else
+               tempenv_assign_error++;
+           }
+       }
+
+      dispose_words (subst_assign_varlist);
+      subst_assign_varlist = (WORD_LIST *)NULL;
+    }
+
+#if 0
+  tint = list_length (new_list) + 1;
+  RESIZE_MALLOCED_BUFFER (glob_argv_flags, 0, tint, glob_argv_flags_size, 16);
+  for (tint = 0, temp_list = new_list; temp_list; temp_list = temp_list->next)
+    glob_argv_flags[tint++] = (temp_list->word->flags & W_GLOBEXP) ? '1' : '0';
+  glob_argv_flags[tint] = '\0';
+#endif
+
+  return (new_list);
+}
index 1da1b6fc4e4ff53a0ff7352b8c8ae5505c2d4514..44f6253cb0bed1bbe92b734411144aac6df90477 100644 (file)
--- a/subst.c~
+++ b/subst.c~
@@ -52,6 +52,7 @@
 #include "mailcheck.h"
 
 #include "shmbutil.h"
+#include "typemax.h"
 
 #include "builtins/getopt.h"
 #include "builtins/common.h"
@@ -2296,11 +2297,7 @@ string_list_dollar_at (list, quoted)
 
   /* XXX -- why call quote_list if ifs == 0?  we can get away without doing
      it now that quote_escapes quotes spaces */
-#if 0
-  tlist = ((quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES)) || (ifs && *ifs == 0))
-#else
   tlist = (quoted & (Q_HERE_DOCUMENT|Q_DOUBLE_QUOTES|Q_PATQUOTE))
-#endif
                ? quote_list (list)
                : list_quote_escapes (list);
 
@@ -2855,8 +2852,9 @@ do_assignment (string)
 }
 
 int
-do_word_assignment (word)
+do_word_assignment (word, flags)
      WORD_DESC *word;
+     int flags;
 {
   return do_assignment_internal (word, 1);
 }
@@ -4003,6 +4001,7 @@ match_upattern (string, pat, mtype, sp, ep)
   int c, len, mlen;
   register char *p, *p1, *npat;
   char *end;
+  int n1;
 
   /* If the pattern doesn't match anywhere in the string, go ahead and
      short-circuit right away.  A minor optimization, saves a bunch of
@@ -4049,8 +4048,15 @@ match_upattern (string, pat, mtype, sp, ep)
              for (p1 = end; p1 >= p; p1--)
 #else
              p1 = (mlen == -1) ? end : p + mlen;
-             /* extra -1 to handle case of p1 == end */
-             if (p1 - p + mlen - 1 > len)
+             /* p1 - p = length of portion of string to be considered
+                p = current position in string
+                mlen = number of characters consumed by match (-1 for entire string)
+                end = end of string
+                we want to break immediately if the potential match len
+                is greater than the number of characters remaining in the
+                string
+             */
+             if (p1 > end)
                break;
              for ( ; p1 >= p; p1--)
 #endif
@@ -4145,7 +4151,13 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
   wchar_t wc, *wp, *nwpat, *wp1;
   size_t len;
   int mlen;
-  int n, n1;
+  int n, n1, n2, simple;
+
+  simple = (wpat[0] != L'\\' && wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'[');
+#if defined (EXTENDED_GLOB)
+  if (extended_glob)
+    simple |= (wpat[1] != L'(' || (wpat[0] != L'*' && wpat[0] != L'?' && wpat[0] != L'+' && wpat[0] != L'!' && wpat[0] != L'@')); /*)*/
+#endif
 
   /* If the pattern doesn't match anywhere in the string, go ahead and
      short-circuit right away.  A minor optimization, saves a bunch of
@@ -4175,13 +4187,19 @@ match_wpattern (wstring, indices, wstrlen, wpat, mtype, sp, ep)
     return (0);
 
   mlen = wmatchlen (wpat, wstrlen);
+
 /* itrace("wmatchlen (%ls) -> %d", wpat, mlen); */
   switch (mtype)
     {
     case MATCH_ANY:
       for (n = 0; n <= wstrlen; n++)
        {
-         if (match_pattern_wchar (wpat, wstring + n))
+#if 1
+         n2 = simple ? (*wpat == wstring[n]) : match_pattern_wchar (wpat, wstring + n);
+#else
+         n2 = match_pattern_wchar (wpat, wstring + n);
+#endif
+         if (n2)
            {
 #if 0
              for (n1 = wstrlen; n1 >= n; n1--)
@@ -4279,6 +4297,7 @@ match_pattern (string, pat, mtype, sp, ep)
   size_t n;
   wchar_t *wstring, *wpat;
   char **indices;
+  size_t slen, plen, mslen, mplen;
 #endif
 
   if (string == 0 || *string == 0 || pat == 0 || *pat == 0)
@@ -4287,6 +4306,13 @@ match_pattern (string, pat, mtype, sp, ep)
 #if defined (HANDLE_MULTIBYTE)
   if (MB_CUR_MAX > 1)
     {
+      slen = STRLEN (string);
+      mslen = MBSLEN (string);
+      plen = STRLEN (pat);
+      mplen = MBSLEN (pat);
+      if (slen == mslen && plen == mplen)
+        return (match_upattern (string, pat, mtype, sp, ep));
+
       n = xdupmbstowcs (&wpat, NULL, pat);
       if (n == (size_t)-1)
        return (match_upattern (string, pat, mtype, sp, ep));
@@ -5846,34 +5872,6 @@ valid_length_expression (name)
          legal_identifier (name + 1));                         /* ${#PS1} */
 }
 
-#if defined (HANDLE_MULTIBYTE)
-size_t
-mbstrlen (s)
-     const char *s;
-{
-  size_t clen, nc;
-  mbstate_t mbs, mbsbak;
-
-  nc = 0;
-  memset (&mbs, 0, sizeof (mbs));
-  mbsbak = mbs;
-  while ((clen = mbrlen(s, MB_CUR_MAX, &mbs)) != 0)
-    {
-      if (MB_INVALIDCH(clen))
-        {
-         clen = 1;     /* assume single byte */
-         mbs = mbsbak;
-        }
-
-      s += clen;
-      nc++;
-      mbsbak = mbs;
-    }
-  return nc;
-}
-#endif
-      
-
 /* Handle the parameter brace expansion that requires us to return the
    length of a parameter. */
 static intmax_t
@@ -5907,7 +5905,7 @@ parameter_brace_expand_length (name)
          break;
        case '!':
          if (last_asynchronous_pid == NO_PID)
-           t = (char *)NULL;
+           t = (char *)NULL;   /* XXX - error if set -u set? */
          else
            t = itos (last_asynchronous_pid);
          break;
@@ -5956,7 +5954,7 @@ parameter_brace_expand_length (name)
          if (list)
            dispose_words (list);
 
-         number = MB_STRLEN (t);
+         number = t ? MB_STRLEN (t) : 0;
          FREE (t);
        }
     }
@@ -7527,11 +7525,14 @@ param_expand (string, sindex, quoted, expanded_something,
             is unset, the parameters are separated by ' '; if $IFS is
             null, the parameters are concatenated. */
          temp = (quoted & (Q_DOUBLE_QUOTES|Q_PATQUOTE)) ? string_list_dollar_star (list) : string_list (list);
-         temp1 = quote_string (temp);
-         if (*temp == 0)
-           tflag |= W_HASQUOTEDNULL;
-         free (temp);
-         temp = temp1;
+         if (temp)
+           {
+             temp1 = quote_string (temp);
+             if (*temp == 0)
+               tflag |= W_HASQUOTEDNULL;
+             free (temp);
+             temp = temp1;
+           }
        }
       else
        {
@@ -8305,7 +8306,7 @@ add_twochars:
              if (list->next)
                {
 #if 0
-                 if (quoted_dollar_at && word->flags & W_NOSPLIT2)
+                 if (quoted_dollar_at && (word->flags & W_NOSPLIT2))
                    temp = string_list_internal (quote_list (list), " ");
                  else
 #endif
@@ -8343,12 +8344,10 @@ add_twochars:
          else
            temp = (char *)NULL;
 
-#if 0
          /* We do not want to add quoted nulls to strings that are only
             partially quoted; we can throw them away. */
-         if (temp == 0 && quoted_state == PARTIALLY_QUOTED)
+         if (temp == 0 && quoted_state == PARTIALLY_QUOTED && (word->flags & (W_NOSPLIT|W_NOSPLIT2)))
            continue;
-#endif
 
        add_quoted_string:
 
@@ -9185,7 +9184,7 @@ shell_expand_word_list (tlist, eflags)
          if (tlist->word->flags & W_ASSIGNASSOC)
            make_internal_declare (tlist->word->word, "-A");
 
-         t = do_word_assignment (tlist->word);
+         t = do_word_assignment (tlist->word, 0);
          if (t == 0)
            {
              last_command_exit_value = EXECUTION_FAILURE;
@@ -9285,7 +9284,7 @@ expand_word_list_internal (list, eflags)
              for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
                {
                  this_command_name = (char *)NULL;     /* no arithmetic errors */
-                 tint = do_word_assignment (temp_list->word);
+                 tint = do_word_assignment (temp_list->word, 0);
                  /* Variable assignment errors in non-interactive shells
                     running in Posix.2 mode cause the shell to exit. */
                  if (tint == 0)
@@ -9334,7 +9333,7 @@ expand_word_list_internal (list, eflags)
   if ((eflags & WEXP_VARASSIGN) && subst_assign_varlist)
     {
       sh_wassign_func_t *assign_func;
-      int is_special_builtin;
+      int is_special_builtin, is_builtin_or_func;
 
       /* If the remainder of the words expand to nothing, Posix.2 requires
         that the variable and environment assignments affect the shell's
@@ -9342,15 +9341,16 @@ expand_word_list_internal (list, eflags)
       assign_func = new_list ? assign_in_env : do_word_assignment;
       tempenv_assign_error = 0;
 
+      is_builtin_or_func = (new_list && new_list->word && (find_shell_builtin (new_list->word->word) || find_function (new_list->word->word)));
       /* Posix says that special builtins exit if a variable assignment error
         occurs in an assignment preceding it. */
-      is_special_builtin = (posixly_correct && new_list && find_special_builtin (new_list->word->word));
+      is_special_builtin = (posixly_correct && new_list && new_list->word && find_special_builtin (new_list->word->word));
       
       for (temp_list = subst_assign_varlist; temp_list; temp_list = temp_list->next)
        {
          this_command_name = (char *)NULL;
          assigning_in_environment = (assign_func == assign_in_env);
-         tint = (*assign_func) (temp_list->word);
+         tint = (*assign_func) (temp_list->word, is_builtin_or_func);
          assigning_in_environment = 0;
          /* Variable assignment errors in non-interactive shells running
             in Posix.2 mode cause the shell to exit. */
diff --git a/subst.h b/subst.h
index da49d9eda606a5480a9cd8277298b887ccfaedeb..ae388fd53dcf68a1cce42954bfd1880acb5c6136 100644 (file)
--- a/subst.h
+++ b/subst.h
@@ -124,7 +124,7 @@ extern char *strip_trailing_ifs_whitespace __P((char *, char *, int));
    splitting on the result of expansion. */
 extern int do_assignment __P((char *));
 extern int do_assignment_no_expand __P((char *));
-extern int do_word_assignment __P((WORD_DESC *));
+extern int do_word_assignment __P((WORD_DESC *, int));
 
 /* Append SOURCE to TARGET at INDEX.  SIZE is the current amount
    of space allocated to TARGET.  SOURCE can be NULL, in which
@@ -260,6 +260,7 @@ extern void unlink_fifo __P((int));
 
 extern char *copy_fifo_list __P((int *));
 extern void unlink_new_fifos __P((char *, int));
+extern void close_new_fifos __P((char *, int));
 
 extern WORD_LIST *list_string_with_quotes __P((char *));
 
index 86864c8be95dbdbfbf55991d9cc0a0ee8a5b7721..bf45e26bf4f2047ef5000c7d9e12ecab88e523c6 100644 (file)
--- a/subst.h~
+++ b/subst.h~
@@ -260,6 +260,7 @@ extern void unlink_fifo __P((int));
 
 extern char *copy_fifo_list __P((int *));
 extern void unlink_new_fifos __P((char *, int));
+extern void close_new_fifos __P((char *, int));
 
 extern WORD_LIST *list_string_with_quotes __P((char *));
 
@@ -277,6 +278,7 @@ extern char *cond_expand_word __P((WORD_DESC *, int));
 #define SD_INVERT      0x02    /* look for chars NOT in passed set */
 #define SD_NOQUOTEDELIM        0x04    /* don't let single or double quotes act as delimiters */
 #define SD_NOSKIPCMD   0x08    /* don't skip over $(, <(, or >( command/process substitution */
+#define SD_EXTGLOB     0x10    /* skip over extended globbing patterns if appropriate */
 
 extern int skip_to_delim __P((char *, int, char *, int));
 
index 3efcf32d68e9722024b6ca9d67f9e81b2aa5ac04..72ec06a2c1fd8dde92acea5e8ac773e35f1d061b 100755 (executable)
@@ -1,4 +1,4 @@
-BUILD_DIR=/usr/local/build/chet/bash/bash-current
+BUILD_DIR=/usr/local/build/bash/bash-current
 THIS_SH=$BUILD_DIR/bash
 PATH=$PATH:$BUILD_DIR
 
index d46a2a16ab6242836853d5b23c9b6a578ec8ca09..959ded275963698e8216364f1f7653608ced20d0 100644 (file)
@@ -166,3 +166,7 @@ graph
 yes
 a
 a
+ val
+val
+ val
+no arg passed
index 1f4a35b782d29034e2292618ab559b46941fcf8e..c97b48cbbdafa6a250459dc1f2264f4e0e7db7e0 100644 (file)
@@ -384,3 +384,5 @@ ${THIS_SH} ./exp1.sub
 ${THIS_SH} ./exp2.sub
 
 ${THIS_SH} ./exp3.sub
+
+${THIS_SH} ./exp4.sub
diff --git a/tests/exp.tests~ b/tests/exp.tests~
new file mode 100644 (file)
index 0000000..1f4a35b
--- /dev/null
@@ -0,0 +1,386 @@
+#
+# A suite of tests for bash word expansions
+#
+# This tests parameter and variable expansion, with an empahsis on
+# proper quoting behavior.
+#
+# Chet Ramey
+
+#
+# If you comment out the body of this function, you can do a diff against
+# `expansion-tests.right' to see if the shell is behaving correctly
+#
+expect()
+{
+       echo expect "$@"
+}
+
+# Test the substitution quoting characters (CTLESC and CTLNUL) in different
+# combinations
+
+expect "<^A>"
+recho `echo '\ 1'`
+expect "<^A>"
+recho `echo "\ 1"`
+expect "<^B>"
+recho `echo '\ 2'`
+expect "<^B>"
+recho `echo "\ 2"`
+expect "<^A>"
+recho `echo \ 1`
+expect "<^B>"
+recho `echo \ 2`
+
+# Test null strings without variable expansion
+expect "<abcdefgh>"
+recho abcd""efgh
+expect "<abcdefgh>"
+recho abcd''efgh
+expect "<abcdefgh>"
+recho ""abcdefgh
+expect "<abcdefgh>"
+recho ''abcdefgh
+expect "<abcd>"
+recho abcd""
+expect "<abcd>"
+recho abcd''
+
+# Test the quirky behavior of $@ in ""
+expect nothing
+recho "$@"
+expect "< >"
+recho " $@"
+expect "<-->"
+recho "-${@}-"
+
+# Test null strings with variable expansion that fails
+expect '<>'
+recho $xxx""
+expect '<>'
+recho ""$xxx
+expect '<>'
+recho $xxx''
+expect '<>'
+recho ''$xxx
+expect '<>'
+recho $xxx""$yyy
+expect '<>'
+recho $xxx''$yyy
+
+# Test null strings with variable expansion that succeeds
+xxx=abc
+yyy=def
+
+expect '<abc>'
+recho $xxx""
+expect '<abc>'
+recho ""$xxx
+expect '<abc>'
+recho $xxx''
+expect '<abc>'
+recho ''$xxx
+expect '<abcdef>'
+recho $xxx""$yyy
+expect '<abcdef>'
+recho $xxx''$yyy
+
+unset xxx yyy
+
+# Test the unquoted special quoting characters
+expect "<^A>"
+recho \ 1
+expect "<^B>"
+recho \ 2
+expect "<^A>"
+recho "\ 1"
+expect "<^B>"
+recho "\ 2"
+expect "<^A>"
+recho '\ 1'
+expect "<^B>"
+recho '\ 2'
+
+# Test expansion of a variable that is unset
+expect nothing
+recho $xxx
+expect '<>'
+recho "$xxx"
+
+expect nothing
+recho "$xxx${@}"
+
+# Test empty string expansion
+expect '<>'
+recho ""
+expect '<>'
+recho ''
+
+# Test command substitution with (disabled) history substitution
+expect '<Hello World!>'
+# set +H
+recho "`echo \"Hello world!\"`"
+
+# Test some shell special characters
+expect '<`>'
+recho "\`"
+expect '<">'
+recho "\""
+expect '<\^A>'
+recho "\\ 1"
+
+expect '<\$>'
+recho "\\$"
+
+expect '<\\>'
+recho "\\\\"
+
+# This should give argv[1] = a argv[2] = b
+expect '<a> <b>'
+FOO=`echo 'a b' | tr ' ' '\012'`
+recho $FOO
+
+# This should give argv[1] = ^A argv[2] = ^B
+expect '<^A> <^B>'
+FOO=`echo '\ 1 \ 2' | tr ' ' '\012'`
+recho $FOO
+
+# Test quoted and unquoted globbing characters
+expect '<**>'
+recho "*"*
+
+expect '<\.\./*/>'
+recho "\.\./*/"
+
+# Test patterns that come up when the shell quotes funny character
+# combinations
+expect '<^A^B^A^B>'
+recho '\ 1\ 2\ 1\ 2'
+expect '<^A^A>'
+recho '\ 1\ 1'
+expect '<^A^B>'
+recho '\ 1\ 2'
+expect '<^A^A^B>'
+recho '\ 1\ 1\ 2'
+
+# More tests of "$@"
+set abc def ghi jkl
+expect '<  abc> <def> <ghi> <jkl  >'
+recho "  $@  "
+expect '<  abc> <def> <ghi> <jkl  >'
+recho "${1+  $@  }"
+
+set abc def ghi jkl
+expect '<--abc> <def> <ghi> <jkl-->'
+recho "--$@--"
+
+set "a b" cd ef gh
+expect '<a b> <cd> <ef> <gh>'
+recho ${1+"$@"}
+expect '<a b> <cd> <ef> <gh>'
+recho ${foo:-"$@"}
+expect '<a b> <cd> <ef> <gh>'
+recho "${@}"
+
+expect '<  >'
+recho "  "
+expect '< - >'
+recho " - "
+
+# Test combinations of different types of quoting in a fully-quoted string
+# (so the WHOLLY_QUOTED tests fail and it doesn't get set)
+expect '</^root:/{s/^[^:]*:[^:]*:\([^:]*\).*$/\1/>'
+recho "/^root:/{s/^[^:]*:[^:]*:\([^:]*\).*"'$'"/\1/"
+
+# Test the various Posix parameter expansions
+
+expect '<foo bar>'
+recho "${x:-$(echo "foo bar")}"
+expect '<foo> <bar>'
+recho ${x:-$(echo "foo bar")}
+
+unset X
+expect '<abc>'
+recho ${X:=abc}
+expect '<abc>'
+recho $X
+
+set a b c
+expect '<posix>'
+recho ${3:+posix}
+
+POSIX=/usr/posix
+expect '<10>'
+recho ${#POSIX}
+
+# remove shortest trailing match
+x=file.c
+expect '<file.o>'
+recho ${x%.c}.o
+
+# remove longest trailing match
+x=posix/src/std
+expect '<posix>'
+recho ${x%%/*}
+
+# remove shortest leading pattern
+x=$HOME/src/cmd
+expect '</src/cmd>'
+recho ${x#$HOME}
+
+# remove longest leading pattern
+x=/one/two/three
+expect '<three>'
+recho ${x##*/}
+
+# pattern removal of patterns that don't match
+z=abcdef
+
+expect '<abcdef>'
+recho ${z#xyz}
+expect '<abcdef>'
+recho ${z##xyz}
+
+expect '<abcdef>'
+recho ${z%xyz}
+expect '<abcdef>'
+recho ${z%%xyz}
+
+# Command substitution and the quirky differences between `` and $()
+
+expect '<\$x>'
+recho '\$x'
+
+expect '<$x>'
+recho `echo '\$x'`
+
+expect '<\$x>'
+recho $(echo '\$x')
+
+# The difference between $* "$*" and "$@"
+
+set "abc" "def ghi" "jkl"
+
+expect '<abc> <def> <ghi> <jkl>'
+recho $*
+
+expect '<abc def ghi jkl>'
+recho "$*"
+
+OIFS="$IFS"
+IFS=":$IFS"
+
+# The special behavior of "$*", using the first character of $IFS as separator
+expect '<abc:def ghi:jkl>'
+recho "$*"
+
+IFS="$OIFS"
+
+expect '<abc> <def ghi> <jkl>'
+recho "$@"
+
+expect '<xxabc> <def ghi> <jklyy>'
+recho "xx$@yy"
+
+expect '<abc> <def ghi> <jklabc> <def ghi> <jkl>'
+recho "$@$@"
+
+foo=abc
+bar=def
+
+expect '<abcdef>'
+recho "$foo""$bar"
+
+unset foo
+set $foo bar '' xyz "$foo" abc
+
+expect '<bar> <> <xyz> <> <abc>'
+recho "$@"
+
+# More tests of quoting and deferred evaluation
+
+foo=10 x=foo
+y='$'$x
+expect '<$foo>'
+recho $y
+eval y='$'$x
+expect '<10>'
+recho $y
+
+# case statements
+
+NL='
+'
+x='ab
+cd'
+
+expect '<newline expected>'
+case "$x" in
+*$NL*) recho "newline expected" ;;
+esac
+
+expect '<got it>'
+case \? in
+*"?"*) recho "got it" ;;
+esac
+
+expect '<got it>'
+case \? in
+*\?*) recho "got it" ;;
+esac
+
+set one two three four five
+expect '<one> <three> <five>'
+recho $1 $3 ${5} $8 ${9}
+
+# length tests on positional parameters and some special parameters
+
+expect '<5> <5>'
+recho $# ${#}
+expect '<3>'
+recho ${#1}
+expect '<1>'
+recho ${##}
+expect '<1>'
+recho ${#?}
+expect '<5>'
+recho ${#@}
+expect '<5>'
+recho ${#*}
+expect '<5>'
+recho "${#@}"
+expect '<5>'
+recho "${#*}"
+
+expect '<42>'
+recho $((28 + 14))
+expect '<26>'
+recho $[ 13 * 2 ]
+
+expect '<\>'
+recho `echo \\\\`
+
+expect '<~>'
+recho '~'
+
+expect nothing
+recho $!
+expect nothing
+recho ${!}
+
+# test word splitting of assignment statements not preceding a command
+a="a b c d e"
+declare b=$a
+expect '<a> <b> <c> <d> <e>'
+recho $b
+
+a="a?b?c"
+
+echo ${a//\\?/ }
+
+echo ${a//\?/ }
+
+${THIS_SH} ./exp1.sub
+
+${THIS_SH} ./exp2.sub
+
+${THIS_SH} ./exp3.sub
diff --git a/tests/exp4.sub b/tests/exp4.sub
new file mode 100644 (file)
index 0000000..d022ceb
--- /dev/null
@@ -0,0 +1,10 @@
+f=" val" e=
+echo "$e"$f
+echo $e$f
+echo "$e""$f"
+
+if [[ "$1"x == x ]] ; then
+   echo "no arg passed"
+   exit 1
+fi
+echo "$1"x | cat -v
index 449d5b09517fa1a8c0b7b91bf79d37e32a5ef417..89536af529db181f2b6446c9487e61c98b6ef1c6 100644 (file)
@@ -11,3 +11,9 @@ aéb
 -абвгдежзиклмноп - 16
 -абвгдежзиклмноп- 15
 -абвгд- 5
+1,0000
+1.0000
+1.0000
+1.0000
+1.0000
+1,0000
index cd919dfb5530c756522a342e1fcedea83e5e6f56..2bff068694c1dcfd3cdae734cde48ae167d1830d 100644 (file)
@@ -39,3 +39,6 @@ printf '%s' "$*" | od -b
 
 # display differences make this problematic
 ${THIS_SH} ./intl1.sub
+
+# this tests both international handling in printf and temporary environments
+${THIS_SH} ./intl2.sub
diff --git a/tests/intl.tests~ b/tests/intl.tests~
new file mode 100644 (file)
index 0000000..cd919df
--- /dev/null
@@ -0,0 +1,41 @@
+export LC_ALL=en_US.UTF-8
+
+a=$'\303\251'
+
+echo "$a"
+
+echo ${#a}
+
+b=$'A\303\251B'
+
+echo "$b"
+
+echo ${b: -1}
+
+c=AeB
+
+echo ${c: -1}
+
+unset a
+a=$(printf '%b' 'A\303\251B')
+IFS=$(printf '%b' '\303\251')
+
+case "$a" in
+"A${IFS}B")    echo ok 1 ;;
+*)             echo bad 1 ;;
+esac
+
+set $a
+
+case $1 in
+A)             echo ok 2 ;;
+*)             echo bad 2 ;;
+esac
+
+set a b
+
+printf '%s\n' "$*"
+printf '%s' "$*" | od -b
+
+# display differences make this problematic
+${THIS_SH} ./intl1.sub
diff --git a/tests/intl2.sub b/tests/intl2.sub
new file mode 100644 (file)
index 0000000..64961bc
--- /dev/null
@@ -0,0 +1,11 @@
+unset LC_ALL LC_NUMERIC
+export LANG=de_DE.UTF-8
+printf '%.4f\n' 1
+
+LANG=C printf '%.4f\n' 1
+LANG=C /usr/bin/printf '%.4f\n' 1
+
+env LANG=C printf '%.4f\n' 1
+(LANG=C; printf '%.4f\n' 1)
+
+printf '%.4f\n' 1
index 5e0f5c1b8664d3bd4ffb9bcfc75513b21b267e3c..a477c03a9b6a6044dd76edad0f1b4effe6fc48cd 100644 (file)
@@ -48,6 +48,7 @@
 #include "unwind_prot.h"
 #include "quit.h"
 #include "sig.h"
+#include "error.h"     /* for internal_warning */
 
 /* Structure describing a saved variable and the value to restore it to.  */
 typedef struct {
index 7552c47812e2f25adff4f007f5025c6eab76bd6b..5ff8afd43dde71d121ade93a8844236eec038b33 100644 (file)
@@ -1235,10 +1235,6 @@ static int seeded_subshell = 0;
 static int
 brand ()
 {
-#if 0
-  rseed = rseed * 1103515245 + 12345;
-  return ((unsigned int)((rseed >> 16) & 32767));      /* was % 32768 */
-#else
   /* From "Random number generators: good ones are hard to find",
      Park and Miller, Communications of the ACM, vol. 31, no. 10,
      October 1988, p. 1195. filtered through FreeBSD */
@@ -1254,7 +1250,6 @@ brand ()
     rseed += 0x7fffffff;
 #endif
   return ((unsigned int)(rseed & 32767));      /* was % 32768 */
-#endif
 }
 
 /* Set the random number generator seed to SEED. */
@@ -2491,8 +2486,9 @@ bind_function_def (name, value)
    responsible for moving the main temporary env to one of the other
    temporary environments.  The expansion code in subst.c calls this. */
 int
-assign_in_env (word)
+assign_in_env (word, flags)
      WORD_DESC *word;
+     int flags;
 {
   int offset;
   char *name, *temp, *value;
@@ -2550,8 +2546,13 @@ assign_in_env (word)
 
   array_needs_making = 1;
 
+#if 0
   if (ifsname (name))
     setifs (var);
+else
+#endif
+  if (flags)
+    stupidly_hack_special_variables (name);
 
   if (echo_command_at_execute)
     /* The Korn shell prints the `+ ' in front of assignment statements,
@@ -3271,6 +3272,9 @@ find_tempenv_variable (name)
   return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL);
 }
 
+char **tempvar_list;
+int tvlist_ind;
+
 /* Push the variable described by (SHELL_VAR *)DATA down to the next
    variable context from the temporary environment. */
 static void
@@ -3306,6 +3310,9 @@ push_temp_var (data)
     }
   v->attributes |= var->attributes;
 
+  if (find_special_var (var->name) >= 0)
+    tempvar_list[tvlist_ind++] = savestring (var->name);
+
   dispose_variable (var);
 }
 
@@ -3319,24 +3326,46 @@ propagate_temp_var (data)
   if (tempvar_p (var) && (var->attributes & att_propagate))
     push_temp_var (data);
   else
-    dispose_variable (var);
+    {
+      if (find_special_var (var->name) >= 0)
+       tempvar_list[tvlist_ind++] = savestring (var->name);
+      dispose_variable (var);
+    }
 }
 
 /* Free the storage used in the hash table for temporary
    environment variables.  PUSHF is a function to be called
    to free each hash table entry.  It takes care of pushing variables
-   to previous scopes if appropriate. */
+   to previous scopes if appropriate.  PUSHF stores names of variables
+   that require special handling (e.g., IFS) on tempvar_list, so this
+   function can call stupidly_hack_special_variables on all the
+   variables in the list when the temporary hash table is destroyed. */
 static void
 dispose_temporary_env (pushf)
      sh_free_func_t *pushf;
 {
+  int i;
+
+  tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1);
+  tempvar_list[tvlist_ind = 0] = 0;
+    
   hash_flush (temporary_env, pushf);
   hash_dispose (temporary_env);
   temporary_env = (HASH_TABLE *)NULL;
 
+  tempvar_list[tvlist_ind] = 0;
+
   array_needs_making = 1;
 
-  sv_ifs ("IFS");              /* XXX here for now */
+#if 0
+  sv_ifs ("IFS");              /* XXX here for now -- check setifs in assign_in_env */  
+#endif
+  for (i = 0; i < tvlist_ind; i++)
+    stupidly_hack_special_variables (tempvar_list[i]);
+
+  strvec_dispose (tempvar_list);
+  tempvar_list = 0;
+  tvlist_ind = 0;
 }
 
 void
index 78fd9da2c65847e8f4464bea73a9038b6958d8a0..23cdd4873eeb93c79af2b12d86be6b75c7912358 100644 (file)
@@ -83,7 +83,7 @@ extern char **environ;
 
 /* Variables used here and defined in other files. */
 extern int posixly_correct;
-extern int line_number;
+extern int line_number, line_number_base;
 extern int subshell_environment, indirection_level, subshell_level;
 extern int build_version, patch_level;
 extern int expanding_redir;
@@ -1235,10 +1235,6 @@ static int seeded_subshell = 0;
 static int
 brand ()
 {
-#if 0
-  rseed = rseed * 1103515245 + 12345;
-  return ((unsigned int)((rseed >> 16) & 32767));      /* was % 32768 */
-#else
   /* From "Random number generators: good ones are hard to find",
      Park and Miller, Communications of the ACM, vol. 31, no. 10,
      October 1988, p. 1195. filtered through FreeBSD */
@@ -1254,7 +1250,6 @@ brand ()
     rseed += 0x7fffffff;
 #endif
   return ((unsigned int)(rseed & 32767));      /* was % 32768 */
-#endif
 }
 
 /* Set the random number generator seed to SEED. */
@@ -1336,7 +1331,7 @@ assign_lineno (var, value, unused, key)
 
   if (value == 0 || *value == '\0' || legal_number (value, &new_value) == 0)
     new_value = 0;
-  line_number = new_value;
+  line_number = line_number_base = new_value;
   return var;
 }
 
@@ -2491,8 +2486,9 @@ bind_function_def (name, value)
    responsible for moving the main temporary env to one of the other
    temporary environments.  The expansion code in subst.c calls this. */
 int
-assign_in_env (word)
+assign_in_env (word, flags)
      WORD_DESC *word;
+     int flags;
 {
   int offset;
   char *name, *temp, *value;
@@ -2550,8 +2546,13 @@ assign_in_env (word)
 
   array_needs_making = 1;
 
+#if 0
   if (ifsname (name))
     setifs (var);
+else
+#endif
+  if (flags)
+    stupidly_hack_special_variables (name);
 
   if (echo_command_at_execute)
     /* The Korn shell prints the `+ ' in front of assignment statements,
@@ -3271,6 +3272,9 @@ find_tempenv_variable (name)
   return (temporary_env ? hash_lookup (name, temporary_env) : (SHELL_VAR *)NULL);
 }
 
+char **tempvar_list;
+int tvlist_ind;
+
 /* Push the variable described by (SHELL_VAR *)DATA down to the next
    variable context from the temporary environment. */
 static void
@@ -3306,6 +3310,9 @@ push_temp_var (data)
     }
   v->attributes |= var->attributes;
 
+  if (find_special_var (var->name))
+    tempvar_list[tvlist_ind++] = savestring (var->name);
+
   dispose_variable (var);
 }
 
@@ -3319,24 +3326,46 @@ propagate_temp_var (data)
   if (tempvar_p (var) && (var->attributes & att_propagate))
     push_temp_var (data);
   else
-    dispose_variable (var);
+    {
+      if (find_special_var (var->name))
+       tempvar_list[tvlist_ind++] = savestring (var->name);
+      dispose_variable (var);
+    }
 }
 
 /* Free the storage used in the hash table for temporary
    environment variables.  PUSHF is a function to be called
    to free each hash table entry.  It takes care of pushing variables
-   to previous scopes if appropriate. */
+   to previous scopes if appropriate.  PUSHF stores names of variables
+   that require special handling (e.g., IFS) on tempvar_list, so this
+   function can call stupidly_hack_special_variables on all the
+   variables in the list when the temporary hash table is destroyed. */
 static void
 dispose_temporary_env (pushf)
      sh_free_func_t *pushf;
 {
+  int i;
+
+  tempvar_list = strvec_create (HASH_ENTRIES (temporary_env) + 1);
+  tempvar_list[tvlist_ind = 0] = 0;
+    
   hash_flush (temporary_env, pushf);
   hash_dispose (temporary_env);
   temporary_env = (HASH_TABLE *)NULL;
 
+  tempvar_list[tvlist_ind] = 0;
+
   array_needs_making = 1;
 
-  sv_ifs ("IFS");              /* XXX here for now */
+#if 0
+  sv_ifs ("IFS");              /* XXX here for now -- check setifs in assign_in_env */  
+#endif
+  for (i = 0; i < tvlist_ind; i++)
+    stupidly_hack_special_variables (tempvar_list[i]);
+
+  strvec_dispose (tempvar_list);
+  tempvar_list = 0;
+  tvlist_ind = 0;
 }
 
 void
index 637539be2a775d77ad6e5c04e5ac68c27d033f55..7041ca94d562a6ce1238be78c45a7e09668eaa69 100644 (file)
@@ -270,7 +270,7 @@ extern SHELL_VAR *bind_variable_value __P((SHELL_VAR *, char *, int));
 extern SHELL_VAR *bind_int_variable __P((char *, char *));
 extern SHELL_VAR *bind_var_to_int __P((char *, intmax_t));
 
-extern int assign_in_env __P((WORD_DESC *));
+extern int assign_in_env __P((WORD_DESC *, int));
 
 extern int unbind_variable __P((const char *));
 extern int unbind_func __P((const char *));
index 513fda3bc62abe75c51bd19dccb54f91a77cfbd5..637539be2a775d77ad6e5c04e5ac68c27d033f55 100644 (file)
@@ -1,6 +1,6 @@
 /* variables.h -- data structures for shell variables. */
 
-/* Copyright (C) 1987-2009 Free Software Foundation, Inc.
+/* Copyright (C) 1987-2010 Free Software Foundation, Inc.
 
    This file is part of GNU Bash, the Bourne Again SHell.
 
diff --git a/y.tab.c b/y.tab.c
index df44d85f59a9be21a7128573149eefa86761326f..67bcea901b1f306e07a625de31ef0cb2d0c66069 100644 (file)
--- a/y.tab.c
+++ b/y.tab.c
@@ -1,7 +1,9 @@
-/* A Bison parser, made by GNU Bison 2.0.  */
+/* A Bison parser, made by GNU Bison 2.3.  */
 
-/* Skeleton parser for Yacc-like parsing with Bison,
-   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
+/* Skeleton implementation for Bison's Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+   Free Software Foundation, Inc.
 
    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
 
    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.  */
+   Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
 
-/* As a special exception, when this file is copied by Bison into a
-   Bison output file, you may use that output file without restriction.
-   This special exception was added by the Free Software Foundation
-   in version 1.24 of Bison.  */
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
 
-/* Written by Richard Stallman by simplifying the original so called
-   ``semantic'' parser.  */
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+   simplifying the original so-called "semantic" parser.  */
 
 /* All symbols defined below should begin with yy or YY, to avoid
    infringing on user name space.  This should be done even for local
@@ -36,6 +46,9 @@
 /* Identify Bison output.  */
 #define YYBISON 1
 
+/* Bison version.  */
+#define YYBISON_VERSION "2.3"
+
 /* Skeleton name.  */
 #define YYSKELETON_NAME "yacc.c"
 
      yacc_EOF = 304
    };
 #endif
+/* Tokens.  */
 #define IF 258
 #define THEN 259
 #define ELSE 260
@@ -470,9 +484,15 @@ static REDIRECTEE redir;
 # define YYERROR_VERBOSE 0
 #endif
 
-#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
+/* Enabling the token table.  */
+#ifndef YYTOKEN_TABLE
+# define YYTOKEN_TABLE 0
+#endif
+
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
 #line 323 "parse.y"
-typedef union YYSTYPE {
+{
   WORD_DESC *word;             /* the word that we read. */
   int number;                  /* the number that we read. */
   WORD_LIST *word_list;
@@ -480,9 +500,10 @@ typedef union YYSTYPE {
   REDIRECT *redirect;
   ELEMENT element;
   PATTERN_LIST *pattern;
-} YYSTYPE;
-/* Line 185 of yacc.c.  */
-#line 486 "y.tab.c"
+}
+/* Line 187 of yacc.c.  */
+#line 506 "y.tab.c"
+       YYSTYPE;
 # define yystype YYSTYPE /* obsolescent; will be withdrawn */
 # define YYSTYPE_IS_DECLARED 1
 # define YYSTYPE_IS_TRIVIAL 1
@@ -493,17 +514,94 @@ typedef union YYSTYPE {
 /* Copy the second part of user declarations.  */
 
 
-/* Line 213 of yacc.c.  */
-#line 498 "y.tab.c"
+/* Line 216 of yacc.c.  */
+#line 519 "y.tab.c"
+
+#ifdef short
+# undef short
+#endif
+
+#ifdef YYTYPE_UINT8
+typedef YYTYPE_UINT8 yytype_uint8;
+#else
+typedef unsigned char yytype_uint8;
+#endif
+
+#ifdef YYTYPE_INT8
+typedef YYTYPE_INT8 yytype_int8;
+#elif (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+typedef signed char yytype_int8;
+#else
+typedef short int yytype_int8;
+#endif
+
+#ifdef YYTYPE_UINT16
+typedef YYTYPE_UINT16 yytype_uint16;
+#else
+typedef unsigned short int yytype_uint16;
+#endif
+
+#ifdef YYTYPE_INT16
+typedef YYTYPE_INT16 yytype_int16;
+#else
+typedef short int yytype_int16;
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+#  define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+#  define YYSIZE_T size_t
+# elif ! defined YYSIZE_T && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+#  include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+#  define YYSIZE_T size_t
+# else
+#  define YYSIZE_T unsigned int
+# endif
+#endif
 
-#if ! defined (yyoverflow) || YYERROR_VERBOSE
+#define YYSIZE_MAXIMUM ((YYSIZE_T) -1)
 
-# ifndef YYFREE
-#  define YYFREE free
+#ifndef YY_
+# if YYENABLE_NLS
+#  if ENABLE_NLS
+#   include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+#   define YY_(msgid) dgettext ("bison-runtime", msgid)
+#  endif
 # endif
-# ifndef YYMALLOC
-#  define YYMALLOC malloc
+# ifndef YY_
+#  define YY_(msgid) msgid
 # endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E.  */
+#if ! defined lint || defined __GNUC__
+# define YYUSE(e) ((void) (e))
+#else
+# define YYUSE(e) /* empty */
+#endif
+
+/* Identity function, used to suppress warnings about constant conditions.  */
+#ifndef lint
+# define YYID(n) (n)
+#else
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static int
+YYID (int i)
+#else
+static int
+YYID (i)
+    int i;
+#endif
+{
+  return i;
+}
+#endif
+
+#if ! defined yyoverflow || YYERROR_VERBOSE
 
 /* The parser invokes alloca or malloc; define the necessary symbols.  */
 
@@ -511,34 +609,76 @@ typedef union YYSTYPE {
 #  if YYSTACK_USE_ALLOCA
 #   ifdef __GNUC__
 #    define YYSTACK_ALLOC __builtin_alloca
+#   elif defined __BUILTIN_VA_ARG_INCR
+#    include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+#   elif defined _AIX
+#    define YYSTACK_ALLOC __alloca
+#   elif defined _MSC_VER
+#    include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+#    define alloca _alloca
 #   else
 #    define YYSTACK_ALLOC alloca
+#    if ! defined _ALLOCA_H && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+#     include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+#     ifndef _STDLIB_H
+#      define _STDLIB_H 1
+#     endif
+#    endif
 #   endif
 #  endif
 # endif
 
 # ifdef YYSTACK_ALLOC
-   /* Pacify GCC's `empty if-body' warning. */
-#  define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
-# else
-#  if defined (__STDC__) || defined (__cplusplus)
-#   include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
-#   define YYSIZE_T size_t
+   /* Pacify GCC's `empty if-body' warning.  */
+#  define YYSTACK_FREE(Ptr) do { /* empty */; } while (YYID (0))
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+    /* The OS might guarantee only one guard page at the bottom of the stack,
+       and a page size can be as small as 4096 bytes.  So we cannot safely
+       invoke alloca (N) if N exceeds 4096.  Use a slightly smaller number
+       to allow for a few compiler-allocated temporary stack slots.  */
+#   define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
 #  endif
+# else
 #  define YYSTACK_ALLOC YYMALLOC
 #  define YYSTACK_FREE YYFREE
+#  ifndef YYSTACK_ALLOC_MAXIMUM
+#   define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+#  endif
+#  if (defined __cplusplus && ! defined _STDLIB_H \
+       && ! ((defined YYMALLOC || defined malloc) \
+            && (defined YYFREE || defined free)))
+#   include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+#   ifndef _STDLIB_H
+#    define _STDLIB_H 1
+#   endif
+#  endif
+#  ifndef YYMALLOC
+#   define YYMALLOC malloc
+#   if ! defined malloc && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
+#  ifndef YYFREE
+#   define YYFREE free
+#   if ! defined free && ! defined _STDLIB_H && (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+#   endif
+#  endif
 # endif
-#endif /* ! defined (yyoverflow) || YYERROR_VERBOSE */
+#endif /* ! defined yyoverflow || YYERROR_VERBOSE */
 
 
-#if (! defined (yyoverflow) \
-     && (! defined (__cplusplus) \
-        || (defined (YYSTYPE_IS_TRIVIAL) && YYSTYPE_IS_TRIVIAL)))
+#if (! defined yyoverflow \
+     && (! defined __cplusplus \
+        || (defined YYSTYPE_IS_TRIVIAL && YYSTYPE_IS_TRIVIAL)))
 
 /* A type that is properly aligned for any stack member.  */
 union yyalloc
 {
-  short int yyss;
+  yytype_int16 yyss;
   YYSTYPE yyvs;
   };
 
@@ -548,24 +688,24 @@ union yyalloc
 /* The size of an array large to enough to hold all stacks, each with
    N elements.  */
 # define YYSTACK_BYTES(N) \
-     ((N) * (sizeof (short int) + sizeof (YYSTYPE))                    \
+     ((N) * (sizeof (yytype_int16) + sizeof (YYSTYPE)) \
       + YYSTACK_GAP_MAXIMUM)
 
 /* Copy COUNT objects from FROM to TO.  The source and destination do
    not overlap.  */
 # ifndef YYCOPY
-#  if defined (__GNUC__) && 1 < __GNUC__
+#  if defined __GNUC__ && 1 < __GNUC__
 #   define YYCOPY(To, From, Count) \
       __builtin_memcpy (To, From, (Count) * sizeof (*(From)))
 #  else
 #   define YYCOPY(To, From, Count)             \
       do                                       \
        {                                       \
-         register YYSIZE_T yyi;                \
+         YYSIZE_T yyi;                         \
          for (yyi = 0; yyi < (Count); yyi++)   \
            (To)[yyi] = (From)[yyi];            \
        }                                       \
-      while (0)
+      while (YYID (0))
 #  endif
 # endif
 
@@ -583,39 +723,33 @@ union yyalloc
        yynewbytes = yystacksize * sizeof (*Stack) + YYSTACK_GAP_MAXIMUM; \
        yyptr += yynewbytes / sizeof (*yyptr);                          \
       }                                                                        \
-    while (0)
-
-#endif
+    while (YYID (0))
 
-#if defined (__STDC__) || defined (__cplusplus)
-   typedef signed char yysigned_char;
-#else
-   typedef short int yysigned_char;
 #endif
 
-/* YYFINAL -- State number of the termination state. */
+/* YYFINAL -- State number of the termination state.  */
 #define YYFINAL  116
 /* YYLAST -- Last index in YYTABLE.  */
 #define YYLAST   659
 
-/* YYNTOKENS -- Number of terminals. */
+/* YYNTOKENS -- Number of terminals.  */
 #define YYNTOKENS  61
-/* YYNNTS -- Number of nonterminals. */
+/* YYNNTS -- Number of nonterminals.  */
 #define YYNNTS  38
-/* YYNRULES -- Number of rules. */
+/* YYNRULES -- Number of rules.  */
 #define YYNRULES  167
-/* YYNRULES -- Number of states. */
+/* YYNRULES -- Number of states.  */
 #define YYNSTATES  342
 
 /* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX.  */
 #define YYUNDEFTOK  2
 #define YYMAXUTOK   304
 
-#define YYTRANSLATE(YYX)                                               \
+#define YYTRANSLATE(YYX)                                               \
   ((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
 
 /* YYTRANSLATE[YYLEX] -- Bison symbol number corresponding to YYLEX.  */
-static const unsigned char yytranslate[] =
+static const yytype_uint8 yytranslate[] =
 {
        0,     2,     2,     2,     2,     2,     2,     2,     2,     2,
       51,     2,     2,     2,     2,     2,     2,     2,     2,     2,
@@ -653,7 +787,7 @@ static const unsigned char yytranslate[] =
 #if YYDEBUG
 /* YYPRHS[YYN] -- Index of the first RHS symbol of rule number YYN in
    YYRHS.  */
-static const unsigned short int yyprhs[] =
+static const yytype_uint16 yyprhs[] =
 {
        0,     0,     3,     6,     8,    11,    13,    15,    18,    21,
       24,    28,    32,    36,    40,    43,    47,    51,    54,    58,
@@ -674,8 +808,8 @@ static const unsigned short int yyprhs[] =
      655,   658,   661,   666,   671,   673,   675,   678
 };
 
-/* YYRHS -- A `-1'-separated list of the rules' RHS. */
-static const yysigned_char yyrhs[] =
+/* YYRHS -- A `-1'-separated list of the rules' RHS.  */
+static const yytype_int8 yyrhs[] =
 {
       62,     0,    -1,    94,    91,    -1,    51,    -1,     1,    51,
       -1,    52,    -1,    26,    -1,    63,    26,    -1,    54,    26,
@@ -749,7 +883,7 @@ static const yysigned_char yyrhs[] =
 };
 
 /* YYRLINE[YYN] -- source line where rule number YYN was defined.  */
-static const unsigned short int yyrline[] =
+static const yytype_uint16 yyrline[] =
 {
        0,   376,   376,   387,   396,   411,   421,   423,   427,   433,
      439,   445,   451,   457,   463,   469,   475,   481,   487,   493,
@@ -771,9 +905,9 @@ static const unsigned short int yyrline[] =
 };
 #endif
 
-#if YYDEBUG || YYERROR_VERBOSE
-/* YYTNME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
-   First, the terminals, then, starting at YYNTOKENS, nonterminals. */
+#if YYDEBUG || YYERROR_VERBOSE || YYTOKEN_TABLE
+/* YYTNAME[SYMBOL-NUM] -- String name of the symbol SYMBOL-NUM.
+   First, the terminals, then, starting at YYNTOKENS, nonterminals.  */
 static const char *const yytname[] =
 {
   "$end", "error", "$undefined", "IF", "THEN", "ELSE", "ELIF", "FI",
@@ -802,7 +936,7 @@ static const char *const yytname[] =
 # ifdef YYPRINT
 /* YYTOKNUM[YYLEX-NUM] -- Internal token number corresponding to
    token YYLEX-NUM.  */
-static const unsigned short int yytoknum[] =
+static const yytype_uint16 yytoknum[] =
 {
        0,   256,   257,   258,   259,   260,   261,   262,   263,   264,
      265,   266,   267,   268,   269,   270,   271,   272,   273,   274,
@@ -815,7 +949,7 @@ static const unsigned short int yytoknum[] =
 # endif
 
 /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives.  */
-static const unsigned char yyr1[] =
+static const yytype_uint8 yyr1[] =
 {
        0,    61,    62,    62,    62,    62,    63,    63,    64,    64,
       64,    64,    64,    64,    64,    64,    64,    64,    64,    64,
@@ -837,7 +971,7 @@ static const unsigned char yyr1[] =
 };
 
 /* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN.  */
-static const unsigned char yyr2[] =
+static const yytype_uint8 yyr2[] =
 {
        0,     2,     2,     1,     2,     1,     1,     2,     2,     2,
        3,     3,     3,     3,     2,     3,     3,     2,     3,     3,
@@ -861,7 +995,7 @@ static const unsigned char yyr2[] =
 /* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state
    STATE-NUM when YYTABLE doesn't specify something else to do.  Zero
    means the default is an error.  */
-static const unsigned char yydefact[] =
+static const yytype_uint8 yydefact[] =
 {
        0,     0,   147,     0,     0,     0,   147,   147,     0,     0,
        0,     0,   165,    52,    53,     0,     0,   111,     0,     0,
@@ -900,8 +1034,8 @@ static const unsigned char yydefact[] =
       91,    92
 };
 
-/* YYDEFGOTO[NTERM-NUM]. */
-static const short int yydefgoto[] =
+/* YYDEFGOTO[NTERM-NUM].  */
+static const yytype_int16 yydefgoto[] =
 {
       -1,    34,   239,    35,    36,   120,    37,    38,    39,    40,
       41,    42,    43,    44,   213,    45,    46,    47,    48,    49,
@@ -912,7 +1046,7 @@ static const short int yydefgoto[] =
 /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
    STATE-NUM.  */
 #define YYPACT_NINF -208
-static const short int yypact[] =
+static const yytype_int16 yypact[] =
 {
      296,   -35,  -208,    -2,    38,    10,  -208,  -208,    24,   546,
       31,   346,    51,    47,  -208,   591,   604,  -208,    56,    71,
@@ -952,7 +1086,7 @@ static const short int yypact[] =
 };
 
 /* YYPGOTO[NTERM-NUM].  */
-static const short int yypgoto[] =
+static const yytype_int16 yypgoto[] =
 {
     -208,  -208,   164,   -37,   -31,   -62,   368,  -208,    -5,  -208,
     -208,  -208,  -208,  -208,  -206,  -208,  -208,  -208,  -208,  -208,
@@ -965,7 +1099,7 @@ static const short int yypgoto[] =
    number is the opposite.  If zero, do what YYDEFACT says.
    If YYTABLE_NINF, syntax error.  */
 #define YYTABLE_NINF -1
-static const unsigned short int yytable[] =
+static const yytype_uint16 yytable[] =
 {
       64,    65,   119,    53,    69,    52,   118,   150,   100,   256,
      130,   101,   137,   139,    75,   144,    56,   142,   148,   260,
@@ -1035,7 +1169,7 @@ static const unsigned short int yytable[] =
       94,    95,     0,     0,     0,     0,     0,     0,    96,    97
 };
 
-static const short int yycheck[] =
+static const yytype_int16 yycheck[] =
 {
        6,     7,    39,     0,     9,     0,    37,    69,    26,   215,
       55,    29,    60,    61,    11,    63,    51,    62,    66,   226,
@@ -1107,7 +1241,7 @@ static const short int yycheck[] =
 
 /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
    symbol of state STATE-NUM.  */
-static const unsigned char yystos[] =
+static const yytype_uint8 yystos[] =
 {
        0,     1,     3,     8,    10,    11,    12,    13,    16,    17,
       18,    22,    23,    26,    27,    28,    29,    30,    35,    36,
@@ -1146,22 +1280,6 @@ static const unsigned char yystos[] =
       15,    58
 };
 
-#if ! defined (YYSIZE_T) && defined (__SIZE_TYPE__)
-# define YYSIZE_T __SIZE_TYPE__
-#endif
-#if ! defined (YYSIZE_T) && defined (size_t)
-# define YYSIZE_T size_t
-#endif
-#if ! defined (YYSIZE_T)
-# if defined (__STDC__) || defined (__cplusplus)
-#  include <stddef.h> /* INFRINGES ON USER NAME SPACE */
-#  define YYSIZE_T size_t
-# endif
-#endif
-#if ! defined (YYSIZE_T)
-# define YYSIZE_T unsigned int
-#endif
-
 #define yyerrok                (yyerrstatus = 0)
 #define yyclearin      (yychar = YYEMPTY)
 #define YYEMPTY                (-2)
@@ -1187,15 +1305,15 @@ do                                                              \
       yychar = (Token);                                                \
       yylval = (Value);                                                \
       yytoken = YYTRANSLATE (yychar);                          \
-      YYPOPSTACK;                                              \
+      YYPOPSTACK (1);                                          \
       goto yybackup;                                           \
     }                                                          \
   else                                                         \
-    {                                                          \
-      yyerror ("syntax error: cannot back up");\
+    {                                                          \
+      yyerror (YY_("syntax error: cannot back up")); \
       YYERROR;                                                 \
     }                                                          \
-while (0)
+while (YYID (0))
 
 
 #define YYTERROR       1
@@ -1210,7 +1328,7 @@ while (0)
 #ifndef YYLLOC_DEFAULT
 # define YYLLOC_DEFAULT(Current, Rhs, N)                               \
     do                                                                 \
-      if (N)                                                           \
+      if (YYID (N))                                                    \
        {                                                               \
          (Current).first_line   = YYRHSLOC (Rhs, 1).first_line;        \
          (Current).first_column = YYRHSLOC (Rhs, 1).first_column;      \
@@ -1224,7 +1342,7 @@ while (0)
          (Current).first_column = (Current).last_column =              \
            YYRHSLOC (Rhs, 0).last_column;                              \
        }                                                               \
-    while (0)
+    while (YYID (0))
 #endif
 
 
@@ -1236,8 +1354,8 @@ while (0)
 # if YYLTYPE_IS_TRIVIAL
 #  define YY_LOCATION_PRINT(File, Loc)                 \
      fprintf (File, "%d.%d-%d.%d",                     \
-              (Loc).first_line, (Loc).first_column,    \
-              (Loc).last_line,  (Loc).last_column)
+             (Loc).first_line, (Loc).first_column,     \
+             (Loc).last_line,  (Loc).last_column)
 # else
 #  define YY_LOCATION_PRINT(File, Loc) ((void) 0)
 # endif
@@ -1264,36 +1382,96 @@ while (0)
 do {                                           \
   if (yydebug)                                 \
     YYFPRINTF Args;                            \
-} while (0)
+} while (YYID (0))
 
-# define YY_SYMBOL_PRINT(Title, Type, Value, Location)         \
-do {                                                           \
-  if (yydebug)                                                 \
-    {                                                          \
-      YYFPRINTF (stderr, "%s ", Title);                                \
-      yysymprint (stderr,                                      \
-                  Type, Value);        \
-      YYFPRINTF (stderr, "\n");                                        \
-    }                                                          \
-} while (0)
+# define YY_SYMBOL_PRINT(Title, Type, Value, Location)                   \
+do {                                                                     \
+  if (yydebug)                                                           \
+    {                                                                    \
+      YYFPRINTF (stderr, "%s ", Title);                                          \
+      yy_symbol_print (stderr,                                           \
+                 Type, Value); \
+      YYFPRINTF (stderr, "\n");                                                  \
+    }                                                                    \
+} while (YYID (0))
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_value_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+#else
+static void
+yy_symbol_value_print (yyoutput, yytype, yyvaluep)
+    FILE *yyoutput;
+    int yytype;
+    YYSTYPE const * const yyvaluep;
+#endif
+{
+  if (!yyvaluep)
+    return;
+# ifdef YYPRINT
+  if (yytype < YYNTOKENS)
+    YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
+# else
+  YYUSE (yyoutput);
+# endif
+  switch (yytype)
+    {
+      default:
+       break;
+    }
+}
+
+
+/*--------------------------------.
+| Print this symbol on YYOUTPUT.  |
+`--------------------------------*/
+
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+static void
+yy_symbol_print (FILE *yyoutput, int yytype, YYSTYPE const * const yyvaluep)
+#else
+static void
+yy_symbol_print (yyoutput, yytype, yyvaluep)
+    FILE *yyoutput;
+    int yytype;
+    YYSTYPE const * const yyvaluep;
+#endif
+{
+  if (yytype < YYNTOKENS)
+    YYFPRINTF (yyoutput, "token %s (", yytname[yytype]);
+  else
+    YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]);
+
+  yy_symbol_value_print (yyoutput, yytype, yyvaluep);
+  YYFPRINTF (yyoutput, ")");
+}
 
 /*------------------------------------------------------------------.
 | yy_stack_print -- Print the state stack from its BOTTOM up to its |
 | TOP (included).                                                   |
 `------------------------------------------------------------------*/
 
-#if defined (__STDC__) || defined (__cplusplus)
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
 static void
-yy_stack_print (short int *bottom, short int *top)
+yy_stack_print (yytype_int16 *bottom, yytype_int16 *top)
 #else
 static void
 yy_stack_print (bottom, top)
-    short int *bottom;
-    short int *top;
+    yytype_int16 *bottom;
+    yytype_int16 *top;
 #endif
 {
   YYFPRINTF (stderr, "Stack now");
-  for (/* Nothing. */; bottom <= top; ++bottom)
+  for (; bottom <= top; ++bottom)
     YYFPRINTF (stderr, " %d", *bottom);
   YYFPRINTF (stderr, "\n");
 }
@@ -1302,37 +1480,45 @@ yy_stack_print (bottom, top)
 do {                                                           \
   if (yydebug)                                                 \
     yy_stack_print ((Bottom), (Top));                          \
-} while (0)
+} while (YYID (0))
 
 
 /*------------------------------------------------.
 | Report that the YYRULE is going to be reduced.  |
 `------------------------------------------------*/
 
-#if defined (__STDC__) || defined (__cplusplus)
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
 static void
-yy_reduce_print (int yyrule)
+yy_reduce_print (YYSTYPE *yyvsp, int yyrule)
 #else
 static void
-yy_reduce_print (yyrule)
+yy_reduce_print (yyvsp, yyrule)
+    YYSTYPE *yyvsp;
     int yyrule;
 #endif
 {
+  int yynrhs = yyr2[yyrule];
   int yyi;
-  unsigned int yylno = yyrline[yyrule];
-  YYFPRINTF (stderr, "Reducing stack by rule %d (line %u), ",
-             yyrule - 1, yylno);
-  /* Print the symbols being reduced, and their result.  */
-  for (yyi = yyprhs[yyrule]; 0 <= yyrhs[yyi]; yyi++)
-    YYFPRINTF (stderr, "%s ", yytname [yyrhs[yyi]]);
-  YYFPRINTF (stderr, "-> %s\n", yytname [yyr1[yyrule]]);
+  unsigned long int yylno = yyrline[yyrule];
+  YYFPRINTF (stderr, "Reducing stack by rule %d (line %lu):\n",
+            yyrule - 1, yylno);
+  /* The symbols being reduced.  */
+  for (yyi = 0; yyi < yynrhs; yyi++)
+    {
+      fprintf (stderr, "   $%d = ", yyi + 1);
+      yy_symbol_print (stderr, yyrhs[yyprhs[yyrule] + yyi],
+                      &(yyvsp[(yyi + 1) - (yynrhs)])
+                                      );
+      fprintf (stderr, "\n");
+    }
 }
 
 # define YY_REDUCE_PRINT(Rule)         \
 do {                                   \
   if (yydebug)                         \
-    yy_reduce_print (Rule);            \
-} while (0)
+    yy_reduce_print (yyvsp, Rule); \
+} while (YYID (0))
 
 /* Nonzero means print parse trace.  It is left uninitialized so that
    multiple parsers can coexist.  */
@@ -1354,7 +1540,7 @@ int yydebug;
    if the built-in stack extension method is used).
 
    Do not make this value too large; the results are undefined if
-   SIZE_MAX < YYSTACK_BYTES (YYMAXDEPTH)
+   YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
    evaluated with infinite-precision integer arithmetic.  */
 
 #ifndef YYMAXDEPTH
@@ -1366,45 +1552,47 @@ int yydebug;
 #if YYERROR_VERBOSE
 
 # ifndef yystrlen
-#  if defined (__GLIBC__) && defined (_STRING_H)
+#  if defined __GLIBC__ && defined _STRING_H
 #   define yystrlen strlen
 #  else
 /* Return the length of YYSTR.  */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
 static YYSIZE_T
-#   if defined (__STDC__) || defined (__cplusplus)
 yystrlen (const char *yystr)
-#   else
+#else
+static YYSIZE_T
 yystrlen (yystr)
-     const char *yystr;
-#   endif
+    const char *yystr;
+#endif
 {
-  register const char *yys = yystr;
-
-  while (*yys++ != '\0')
+  YYSIZE_T yylen;
+  for (yylen = 0; yystr[yylen]; yylen++)
     continue;
-
-  return yys - yystr - 1;
+  return yylen;
 }
 #  endif
 # endif
 
 # ifndef yystpcpy
-#  if defined (__GLIBC__) && defined (_STRING_H) && defined (_GNU_SOURCE)
+#  if defined __GLIBC__ && defined _STRING_H && defined _GNU_SOURCE
 #   define yystpcpy stpcpy
 #  else
 /* Copy YYSRC to YYDEST, returning the address of the terminating '\0' in
    YYDEST.  */
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
 static char *
-#   if defined (__STDC__) || defined (__cplusplus)
 yystpcpy (char *yydest, const char *yysrc)
-#   else
+#else
+static char *
 yystpcpy (yydest, yysrc)
-     char *yydest;
-     const char *yysrc;
-#   endif
+    char *yydest;
+    const char *yysrc;
+#endif
 {
-  register char *yyd = yydest;
-  register const char *yys = yysrc;
+  char *yyd = yydest;
+  const char *yys = yysrc;
 
   while ((*yyd++ = *yys++) != '\0')
     continue;
@@ -1414,53 +1602,171 @@ yystpcpy (yydest, yysrc)
 #  endif
 # endif
 
-#endif /* !YYERROR_VERBOSE */
+# ifndef yytnamerr
+/* Copy to YYRES the contents of YYSTR after stripping away unnecessary
+   quotes and backslashes, so that it's suitable for yyerror.  The
+   heuristic is that double-quoting is unnecessary unless the string
+   contains an apostrophe, a comma, or backslash (other than
+   backslash-backslash).  YYSTR is taken from yytname.  If YYRES is
+   null, do not copy; instead, return the length of what the result
+   would have been.  */
+static YYSIZE_T
+yytnamerr (char *yyres, const char *yystr)
+{
+  if (*yystr == '"')
+    {
+      YYSIZE_T yyn = 0;
+      char const *yyp = yystr;
 
-\f
+      for (;;)
+       switch (*++yyp)
+         {
+         case '\'':
+         case ',':
+           goto do_not_strip_quotes;
+
+         case '\\':
+           if (*++yyp != '\\')
+             goto do_not_strip_quotes;
+           /* Fall through.  */
+         default:
+           if (yyres)
+             yyres[yyn] = *yyp;
+           yyn++;
+           break;
+
+         case '"':
+           if (yyres)
+             yyres[yyn] = '\0';
+           return yyn;
+         }
+    do_not_strip_quotes: ;
+    }
 
-#if YYDEBUG
-/*--------------------------------.
-| Print this symbol on YYOUTPUT.  |
-`--------------------------------*/
+  if (! yyres)
+    return yystrlen (yystr);
 
-#if defined (__STDC__) || defined (__cplusplus)
-static void
-yysymprint (FILE *yyoutput, int yytype, YYSTYPE *yyvaluep)
-#else
-static void
-yysymprint (yyoutput, yytype, yyvaluep)
-    FILE *yyoutput;
-    int yytype;
-    YYSTYPE *yyvaluep;
-#endif
+  return yystpcpy (yyres, yystr) - yyres;
+}
+# endif
+
+/* Copy into YYRESULT an error message about the unexpected token
+   YYCHAR while in state YYSTATE.  Return the number of bytes copied,
+   including the terminating null byte.  If YYRESULT is null, do not
+   copy anything; just return the number of bytes that would be
+   copied.  As a special case, return 0 if an ordinary "syntax error"
+   message will do.  Return YYSIZE_MAXIMUM if overflow occurs during
+   size calculation.  */
+static YYSIZE_T
+yysyntax_error (char *yyresult, int yystate, int yychar)
 {
-  /* Pacify ``unused variable'' warnings.  */
-  (void) yyvaluep;
+  int yyn = yypact[yystate];
 
-  if (yytype < YYNTOKENS)
-    YYFPRINTF (yyoutput, "token %s (", yytname[yytype]);
+  if (! (YYPACT_NINF < yyn && yyn <= YYLAST))
+    return 0;
   else
-    YYFPRINTF (yyoutput, "nterm %s (", yytname[yytype]);
+    {
+      int yytype = YYTRANSLATE (yychar);
+      YYSIZE_T yysize0 = yytnamerr (0, yytname[yytype]);
+      YYSIZE_T yysize = yysize0;
+      YYSIZE_T yysize1;
+      int yysize_overflow = 0;
+      enum { YYERROR_VERBOSE_ARGS_MAXIMUM = 5 };
+      char const *yyarg[YYERROR_VERBOSE_ARGS_MAXIMUM];
+      int yyx;
+
+# if 0
+      /* This is so xgettext sees the translatable formats that are
+        constructed on the fly.  */
+      YY_("syntax error, unexpected %s");
+      YY_("syntax error, unexpected %s, expecting %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s or %s");
+      YY_("syntax error, unexpected %s, expecting %s or %s or %s or %s");
+# endif
+      char *yyfmt;
+      char const *yyf;
+      static char const yyunexpected[] = "syntax error, unexpected %s";
+      static char const yyexpecting[] = ", expecting %s";
+      static char const yyor[] = " or %s";
+      char yyformat[sizeof yyunexpected
+                   + sizeof yyexpecting - 1
+                   + ((YYERROR_VERBOSE_ARGS_MAXIMUM - 2)
+                      * (sizeof yyor - 1))];
+      char const *yyprefix = yyexpecting;
+
+      /* Start YYX at -YYN if negative to avoid negative indexes in
+        YYCHECK.  */
+      int yyxbegin = yyn < 0 ? -yyn : 0;
+
+      /* Stay within bounds of both yycheck and yytname.  */
+      int yychecklim = YYLAST - yyn + 1;
+      int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
+      int yycount = 1;
+
+      yyarg[0] = yytname[yytype];
+      yyfmt = yystpcpy (yyformat, yyunexpected);
+
+      for (yyx = yyxbegin; yyx < yyxend; ++yyx)
+       if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR)
+         {
+           if (yycount == YYERROR_VERBOSE_ARGS_MAXIMUM)
+             {
+               yycount = 1;
+               yysize = yysize0;
+               yyformat[sizeof yyunexpected - 1] = '\0';
+               break;
+             }
+           yyarg[yycount++] = yytname[yyx];
+           yysize1 = yysize + yytnamerr (0, yytname[yyx]);
+           yysize_overflow |= (yysize1 < yysize);
+           yysize = yysize1;
+           yyfmt = yystpcpy (yyfmt, yyprefix);
+           yyprefix = yyor;
+         }
 
+      yyf = YY_(yyformat);
+      yysize1 = yysize + yystrlen (yyf);
+      yysize_overflow |= (yysize1 < yysize);
+      yysize = yysize1;
 
-# ifdef YYPRINT
-  if (yytype < YYNTOKENS)
-    YYPRINT (yyoutput, yytoknum[yytype], *yyvaluep);
-# endif
-  switch (yytype)
-    {
-      default:
-        break;
+      if (yysize_overflow)
+       return YYSIZE_MAXIMUM;
+
+      if (yyresult)
+       {
+         /* Avoid sprintf, as that infringes on the user's name space.
+            Don't have undefined behavior even if the translation
+            produced a string with the wrong number of "%s"s.  */
+         char *yyp = yyresult;
+         int yyi = 0;
+         while ((*yyp = *yyf) != '\0')
+           {
+             if (*yyp == '%' && yyf[1] == 's' && yyi < yycount)
+               {
+                 yyp += yytnamerr (yyp, yyarg[yyi++]);
+                 yyf += 2;
+               }
+             else
+               {
+                 yyp++;
+                 yyf++;
+               }
+           }
+       }
+      return yysize;
     }
-  YYFPRINTF (yyoutput, ")");
 }
+#endif /* YYERROR_VERBOSE */
+\f
 
-#endif /* ! YYDEBUG */
 /*-----------------------------------------------.
 | Release the memory associated to this symbol.  |
 `-----------------------------------------------*/
 
-#if defined (__STDC__) || defined (__cplusplus)
+/*ARGSUSED*/
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
 static void
 yydestruct (const char *yymsg, int yytype, YYSTYPE *yyvaluep)
 #else
@@ -1471,8 +1777,7 @@ yydestruct (yymsg, yytype, yyvaluep)
     YYSTYPE *yyvaluep;
 #endif
 {
-  /* Pacify ``unused variable'' warnings.  */
-  (void) yyvaluep;
+  YYUSE (yyvaluep);
 
   if (!yymsg)
     yymsg = "Deleting";
@@ -1482,7 +1787,7 @@ yydestruct (yymsg, yytype, yyvaluep)
     {
 
       default:
-        break;
+       break;
     }
 }
 \f
@@ -1490,13 +1795,13 @@ yydestruct (yymsg, yytype, yyvaluep)
 /* Prevent warnings from -Wmissing-prototypes.  */
 
 #ifdef YYPARSE_PARAM
-# if defined (__STDC__) || defined (__cplusplus)
+#if defined __STDC__ || defined __cplusplus
 int yyparse (void *YYPARSE_PARAM);
-# else
+#else
 int yyparse ();
-# endif
+#endif
 #else /* ! YYPARSE_PARAM */
-#if defined (__STDC__) || defined (__cplusplus)
+#if defined __STDC__ || defined __cplusplus
 int yyparse (void);
 #else
 int yyparse ();
@@ -1521,14 +1826,18 @@ int yynerrs;
 `----------*/
 
 #ifdef YYPARSE_PARAM
-# if defined (__STDC__) || defined (__cplusplus)
-int yyparse (void *YYPARSE_PARAM)
-# else
-int yyparse (YYPARSE_PARAM)
-  void *YYPARSE_PARAM;
-# endif
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
+int
+yyparse (void *YYPARSE_PARAM)
+#else
+int
+yyparse (YYPARSE_PARAM)
+    void *YYPARSE_PARAM;
+#endif
 #else /* ! YYPARSE_PARAM */
-#if defined (__STDC__) || defined (__cplusplus)
+#if (defined __STDC__ || defined __C99__FUNC__ \
+     || defined __cplusplus || defined _MSC_VER)
 int
 yyparse (void)
 #else
@@ -1539,13 +1848,19 @@ yyparse ()
 #endif
 {
   
-  register int yystate;
-  register int yyn;
+  int yystate;
+  int yyn;
   int yyresult;
   /* Number of tokens to shift before error messages enabled.  */
   int yyerrstatus;
   /* Look-ahead token as an internal (translated) token number.  */
   int yytoken = 0;
+#if YYERROR_VERBOSE
+  /* Buffer for error messages, and its allocated size.  */
+  char yymsgbuf[128];
+  char *yymsg = yymsgbuf;
+  YYSIZE_T yymsg_alloc = sizeof yymsgbuf;
+#endif
 
   /* Three stacks and their tools:
      `yyss': related to states,
@@ -1556,18 +1871,18 @@ yyparse ()
      to reallocate them elsewhere.  */
 
   /* The state stack.  */
-  short int yyssa[YYINITDEPTH];
-  short int *yyss = yyssa;
-  register short int *yyssp;
+  yytype_int16 yyssa[YYINITDEPTH];
+  yytype_int16 *yyss = yyssa;
+  yytype_int16 *yyssp;
 
   /* The semantic value stack.  */
   YYSTYPE yyvsa[YYINITDEPTH];
   YYSTYPE *yyvs = yyvsa;
-  register YYSTYPE *yyvsp;
+  YYSTYPE *yyvsp;
 
 
 
-#define YYPOPSTACK   (yyvsp--, yyssp--)
+#define YYPOPSTACK(N)   (yyvsp -= (N), yyssp -= (N))
 
   YYSIZE_T yystacksize = YYINITDEPTH;
 
@@ -1576,9 +1891,9 @@ yyparse ()
   YYSTYPE yyval;
 
 
-  /* When reducing, the number of symbols on the RHS of the reduced
-     rule.  */
-  int yylen;
+  /* The number of symbols on the RHS of the reduced rule.
+     Keep to zero when no symbol should be popped.  */
+  int yylen = 0;
 
   YYDPRINTF ((stderr, "Starting parse\n"));
 
@@ -1595,9 +1910,6 @@ yyparse ()
   yyssp = yyss;
   yyvsp = yyvs;
 
-
-  yyvsp[0] = yylval;
-
   goto yysetstate;
 
 /*------------------------------------------------------------.
@@ -1605,8 +1917,7 @@ yyparse ()
 `------------------------------------------------------------*/
  yynewstate:
   /* In all cases, when you get here, the value and location stacks
-     have just been pushed. so pushing a state here evens the stacks.
-     */
+     have just been pushed.  So pushing a state here evens the stacks.  */
   yyssp++;
 
  yysetstate:
@@ -1619,18 +1930,18 @@ yyparse ()
 
 #ifdef yyoverflow
       {
-       /* Give user a chance to reallocate the stack. Use copies of
+       /* Give user a chance to reallocate the stack.  Use copies of
           these so that the &'s don't force the real ones into
           memory.  */
        YYSTYPE *yyvs1 = yyvs;
-       short int *yyss1 = yyss;
+       yytype_int16 *yyss1 = yyss;
 
 
        /* Each stack pointer address is followed by the size of the
           data in use in that stack, in bytes.  This used to be a
           conditional around just the two extra args, but that might
           be undefined if yyoverflow is a macro.  */
-       yyoverflow ("parser stack overflow",
+       yyoverflow (YY_("memory exhausted"),
                    &yyss1, yysize * sizeof (*yyssp),
                    &yyvs1, yysize * sizeof (*yyvsp),
 
@@ -1641,21 +1952,21 @@ yyparse ()
       }
 #else /* no yyoverflow */
 # ifndef YYSTACK_RELOCATE
-      goto yyoverflowlab;
+      goto yyexhaustedlab;
 # else
       /* Extend the stack our own way.  */
       if (YYMAXDEPTH <= yystacksize)
-       goto yyoverflowlab;
+       goto yyexhaustedlab;
       yystacksize *= 2;
       if (YYMAXDEPTH < yystacksize)
        yystacksize = YYMAXDEPTH;
 
       {
-       short int *yyss1 = yyss;
+       yytype_int16 *yyss1 = yyss;
        union yyalloc *yyptr =
          (union yyalloc *) YYSTACK_ALLOC (YYSTACK_BYTES (yystacksize));
        if (! yyptr)
-         goto yyoverflowlab;
+         goto yyexhaustedlab;
        YYSTACK_RELOCATE (yyss);
        YYSTACK_RELOCATE (yyvs);
 
@@ -1686,12 +1997,10 @@ yyparse ()
 `-----------*/
 yybackup:
 
-/* Do appropriate processing given the current state.  */
-/* Read a look-ahead token if we need one and don't already have one.  */
-/* yyresume: */
+  /* Do appropriate processing given the current state.  Read a
+     look-ahead token if we need one and don't already have one.  */
 
   /* First try to decide what to do without reference to look-ahead token.  */
-
   yyn = yypact[yystate];
   if (yyn == YYPACT_NINF)
     goto yydefault;
@@ -1733,22 +2042,21 @@ yybackup:
   if (yyn == YYFINAL)
     YYACCEPT;
 
+  /* Count tokens shifted since error; after three, turn off error
+     status.  */
+  if (yyerrstatus)
+    yyerrstatus--;
+
   /* Shift the look-ahead token.  */
   YY_SYMBOL_PRINT ("Shifting", yytoken, &yylval, &yylloc);
 
-  /* Discard the token being shifted unless it is eof.  */
+  /* Discard the shifted token unless it is eof.  */
   if (yychar != YYEOF)
     yychar = YYEMPTY;
 
+  yystate = yyn;
   *++yyvsp = yylval;
 
-
-  /* Count tokens shifted since error; after three, turn off error
-     status.  */
-  if (yyerrstatus)
-    yyerrstatus--;
-
-  yystate = yyn;
   goto yynewstate;
 
 
@@ -1788,7 +2096,7 @@ yyreduce:
     {
                          /* Case of regular command.  Discard the error
                             safety net,and return the command just parsed. */
-                         global_command = (yyvsp[-1].command);
+                         global_command = (yyvsp[(1) - (2)].command);
                          eof_encountered = 0;
                          /* discard_parser_constructs (0); */
                          if (parser_state & PST_CMDSUBST)
@@ -1840,19 +2148,19 @@ yyreduce:
 
   case 6:
 #line 422 "parse.y"
-    { (yyval.word_list) = make_word_list ((yyvsp[0].word), (WORD_LIST *)NULL); }
+    { (yyval.word_list) = make_word_list ((yyvsp[(1) - (1)].word), (WORD_LIST *)NULL); }
     break;
 
   case 7:
 #line 424 "parse.y"
-    { (yyval.word_list) = make_word_list ((yyvsp[0].word), (yyvsp[-1].word_list)); }
+    { (yyval.word_list) = make_word_list ((yyvsp[(2) - (2)].word), (yyvsp[(1) - (2)].word_list)); }
     break;
 
   case 8:
 #line 428 "parse.y"
     {
                          source.dest = 1;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_output_direction, redir, 0);
                        }
     break;
@@ -1861,7 +2169,7 @@ yyreduce:
 #line 434 "parse.y"
     {
                          source.dest = 0;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_input_direction, redir, 0);
                        }
     break;
@@ -1869,8 +2177,8 @@ yyreduce:
   case 10:
 #line 440 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_output_direction, redir, 0);
                        }
     break;
@@ -1878,8 +2186,8 @@ yyreduce:
   case 11:
 #line 446 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_input_direction, redir, 0);
                        }
     break;
@@ -1887,8 +2195,8 @@ yyreduce:
   case 12:
 #line 452 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_output_direction, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -1896,8 +2204,8 @@ yyreduce:
   case 13:
 #line 458 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_input_direction, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -1906,7 +2214,7 @@ yyreduce:
 #line 464 "parse.y"
     {
                          source.dest = 1;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_appending_to, redir, 0);
                        }
     break;
@@ -1914,8 +2222,8 @@ yyreduce:
   case 15:
 #line 470 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_appending_to, redir, 0);
                        }
     break;
@@ -1923,8 +2231,8 @@ yyreduce:
   case 16:
 #line 476 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_appending_to, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -1933,7 +2241,7 @@ yyreduce:
 #line 482 "parse.y"
     {
                          source.dest = 1;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_output_force, redir, 0);
                        }
     break;
@@ -1941,8 +2249,8 @@ yyreduce:
   case 18:
 #line 488 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_output_force, redir, 0);
                        }
     break;
@@ -1950,8 +2258,8 @@ yyreduce:
   case 19:
 #line 494 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_output_force, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -1960,7 +2268,7 @@ yyreduce:
 #line 500 "parse.y"
     {
                          source.dest = 0;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_input_output, redir, 0);
                        }
     break;
@@ -1968,8 +2276,8 @@ yyreduce:
   case 21:
 #line 506 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_input_output, redir, 0);
                        }
     break;
@@ -1977,8 +2285,8 @@ yyreduce:
   case 22:
 #line 512 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_input_output, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -1987,7 +2295,7 @@ yyreduce:
 #line 518 "parse.y"
     {
                          source.dest = 0;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_reading_until, redir, 0);
                          redir_stack[need_here_doc++] = (yyval.redirect);
                        }
@@ -1996,8 +2304,8 @@ yyreduce:
   case 24:
 #line 525 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_reading_until, redir, 0);
                          redir_stack[need_here_doc++] = (yyval.redirect);
                        }
@@ -2006,8 +2314,8 @@ yyreduce:
   case 25:
 #line 532 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_reading_until, redir, REDIR_VARASSIGN);
                          redir_stack[need_here_doc++] = (yyval.redirect);
                        }
@@ -2017,7 +2325,7 @@ yyreduce:
 #line 539 "parse.y"
     {
                          source.dest = 0;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_deblank_reading_until, redir, 0);
                          redir_stack[need_here_doc++] = (yyval.redirect);
                        }
@@ -2026,8 +2334,8 @@ yyreduce:
   case 27:
 #line 546 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_deblank_reading_until, redir, 0);
                          redir_stack[need_here_doc++] = (yyval.redirect);
                        }
@@ -2036,8 +2344,8 @@ yyreduce:
   case 28:
 #line 553 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_deblank_reading_until, redir, REDIR_VARASSIGN);
                          redir_stack[need_here_doc++] = (yyval.redirect);
                        }
@@ -2047,7 +2355,7 @@ yyreduce:
 #line 560 "parse.y"
     {
                          source.dest = 0;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_reading_string, redir, 0);
                        }
     break;
@@ -2055,8 +2363,8 @@ yyreduce:
   case 30:
 #line 566 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_reading_string, redir, 0);
                        }
     break;
@@ -2064,8 +2372,8 @@ yyreduce:
   case 31:
 #line 572 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_reading_string, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -2074,7 +2382,7 @@ yyreduce:
 #line 578 "parse.y"
     {
                          source.dest = 0;
-                         redir.dest = (yyvsp[0].number);
+                         redir.dest = (yyvsp[(2) - (2)].number);
                          (yyval.redirect) = make_redirection (source, r_duplicating_input, redir, 0);
                        }
     break;
@@ -2082,8 +2390,8 @@ yyreduce:
   case 33:
 #line 584 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.dest = (yyvsp[0].number);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.dest = (yyvsp[(3) - (3)].number);
                          (yyval.redirect) = make_redirection (source, r_duplicating_input, redir, 0);
                        }
     break;
@@ -2091,8 +2399,8 @@ yyreduce:
   case 34:
 #line 590 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.dest = (yyvsp[0].number);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.dest = (yyvsp[(3) - (3)].number);
                          (yyval.redirect) = make_redirection (source, r_duplicating_input, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -2101,7 +2409,7 @@ yyreduce:
 #line 596 "parse.y"
     {
                          source.dest = 1;
-                         redir.dest = (yyvsp[0].number);
+                         redir.dest = (yyvsp[(2) - (2)].number);
                          (yyval.redirect) = make_redirection (source, r_duplicating_output, redir, 0);
                        }
     break;
@@ -2109,8 +2417,8 @@ yyreduce:
   case 36:
 #line 602 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.dest = (yyvsp[0].number);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.dest = (yyvsp[(3) - (3)].number);
                          (yyval.redirect) = make_redirection (source, r_duplicating_output, redir, 0);
                        }
     break;
@@ -2118,8 +2426,8 @@ yyreduce:
   case 37:
 #line 608 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.dest = (yyvsp[0].number);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.dest = (yyvsp[(3) - (3)].number);
                          (yyval.redirect) = make_redirection (source, r_duplicating_output, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -2128,7 +2436,7 @@ yyreduce:
 #line 614 "parse.y"
     {
                          source.dest = 0;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_duplicating_input_word, redir, 0);
                        }
     break;
@@ -2136,8 +2444,8 @@ yyreduce:
   case 39:
 #line 620 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_duplicating_input_word, redir, 0);
                        }
     break;
@@ -2145,8 +2453,8 @@ yyreduce:
   case 40:
 #line 626 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_duplicating_input_word, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -2155,7 +2463,7 @@ yyreduce:
 #line 632 "parse.y"
     {
                          source.dest = 1;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_duplicating_output_word, redir, 0);
                        }
     break;
@@ -2163,8 +2471,8 @@ yyreduce:
   case 42:
 #line 638 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
-                         redir.filename = (yyvsp[0].word);
+                         source.dest = (yyvsp[(1) - (3)].number);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_duplicating_output_word, redir, 0);
                        }
     break;
@@ -2172,8 +2480,8 @@ yyreduce:
   case 43:
 #line 644 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
-                         redir.filename = (yyvsp[0].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
+                         redir.filename = (yyvsp[(3) - (3)].word);
                          (yyval.redirect) = make_redirection (source, r_duplicating_output_word, redir, REDIR_VARASSIGN);
                        }
     break;
@@ -2190,7 +2498,7 @@ yyreduce:
   case 45:
 #line 656 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
+                         source.dest = (yyvsp[(1) - (3)].number);
                          redir.dest = 0;
                          (yyval.redirect) = make_redirection (source, r_close_this, redir, 0);
                        }
@@ -2199,7 +2507,7 @@ yyreduce:
   case 46:
 #line 662 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
                          redir.dest = 0;
                          (yyval.redirect) = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN);
                        }
@@ -2217,7 +2525,7 @@ yyreduce:
   case 48:
 #line 674 "parse.y"
     {
-                         source.dest = (yyvsp[-2].number);
+                         source.dest = (yyvsp[(1) - (3)].number);
                          redir.dest = 0;
                          (yyval.redirect) = make_redirection (source, r_close_this, redir, 0);
                        }
@@ -2226,7 +2534,7 @@ yyreduce:
   case 49:
 #line 680 "parse.y"
     {
-                         source.filename = (yyvsp[-2].word);
+                         source.filename = (yyvsp[(1) - (3)].word);
                          redir.dest = 0;
                          (yyval.redirect) = make_redirection (source, r_close_this, redir, REDIR_VARASSIGN);
                        }
@@ -2236,7 +2544,7 @@ yyreduce:
 #line 686 "parse.y"
     {
                          source.dest = 1;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_err_and_out, redir, 0);
                        }
     break;
@@ -2245,30 +2553,30 @@ yyreduce:
 #line 692 "parse.y"
     {
                          source.dest = 1;
-                         redir.filename = (yyvsp[0].word);
+                         redir.filename = (yyvsp[(2) - (2)].word);
                          (yyval.redirect) = make_redirection (source, r_append_err_and_out, redir, 0);
                        }
     break;
 
   case 52:
 #line 700 "parse.y"
-    { (yyval.element).word = (yyvsp[0].word); (yyval.element).redirect = 0; }
+    { (yyval.element).word = (yyvsp[(1) - (1)].word); (yyval.element).redirect = 0; }
     break;
 
   case 53:
 #line 702 "parse.y"
-    { (yyval.element).word = (yyvsp[0].word); (yyval.element).redirect = 0; }
+    { (yyval.element).word = (yyvsp[(1) - (1)].word); (yyval.element).redirect = 0; }
     break;
 
   case 54:
 #line 704 "parse.y"
-    { (yyval.element).redirect = (yyvsp[0].redirect); (yyval.element).word = 0; }
+    { (yyval.element).redirect = (yyvsp[(1) - (1)].redirect); (yyval.element).word = 0; }
     break;
 
   case 55:
 #line 708 "parse.y"
     {
-                         (yyval.redirect) = (yyvsp[0].redirect);
+                         (yyval.redirect) = (yyvsp[(1) - (1)].redirect);
                        }
     break;
 
@@ -2277,31 +2585,31 @@ yyreduce:
     {
                          register REDIRECT *t;
 
-                         for (t = (yyvsp[-1].redirect); t->next; t = t->next)
+                         for (t = (yyvsp[(1) - (2)].redirect); t->next; t = t->next)
                            ;
-                         t->next = (yyvsp[0].redirect);
-                         (yyval.redirect) = (yyvsp[-1].redirect);
+                         t->next = (yyvsp[(2) - (2)].redirect);
+                         (yyval.redirect) = (yyvsp[(1) - (2)].redirect);
                        }
     break;
 
   case 57:
 #line 723 "parse.y"
-    { (yyval.command) = make_simple_command ((yyvsp[0].element), (COMMAND *)NULL); }
+    { (yyval.command) = make_simple_command ((yyvsp[(1) - (1)].element), (COMMAND *)NULL); }
     break;
 
   case 58:
 #line 725 "parse.y"
-    { (yyval.command) = make_simple_command ((yyvsp[0].element), (yyvsp[-1].command)); }
+    { (yyval.command) = make_simple_command ((yyvsp[(2) - (2)].element), (yyvsp[(1) - (2)].command)); }
     break;
 
   case 59:
 #line 729 "parse.y"
-    { (yyval.command) = clean_simple_command ((yyvsp[0].command)); }
+    { (yyval.command) = clean_simple_command ((yyvsp[(1) - (1)].command)); }
     break;
 
   case 60:
 #line 731 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 61:
@@ -2309,89 +2617,89 @@ yyreduce:
     {
                          COMMAND *tc;
 
-                         tc = (yyvsp[-1].command);
+                         tc = (yyvsp[(1) - (2)].command);
                          if (tc->redirects)
                            {
                              register REDIRECT *t;
                              for (t = tc->redirects; t->next; t = t->next)
                                ;
-                             t->next = (yyvsp[0].redirect);
+                             t->next = (yyvsp[(2) - (2)].redirect);
                            }
                          else
-                           tc->redirects = (yyvsp[0].redirect);
-                         (yyval.command) = (yyvsp[-1].command);
+                           tc->redirects = (yyvsp[(2) - (2)].redirect);
+                         (yyval.command) = (yyvsp[(1) - (2)].command);
                        }
     break;
 
   case 62:
 #line 749 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 63:
 #line 751 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 64:
 #line 755 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 65:
 #line 757 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 66:
 #line 759 "parse.y"
-    { (yyval.command) = make_while_command ((yyvsp[-3].command), (yyvsp[-1].command)); }
+    { (yyval.command) = make_while_command ((yyvsp[(2) - (5)].command), (yyvsp[(4) - (5)].command)); }
     break;
 
   case 67:
 #line 761 "parse.y"
-    { (yyval.command) = make_until_command ((yyvsp[-3].command), (yyvsp[-1].command)); }
+    { (yyval.command) = make_until_command ((yyvsp[(2) - (5)].command), (yyvsp[(4) - (5)].command)); }
     break;
 
   case 68:
 #line 763 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 69:
 #line 765 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 70:
 #line 767 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 71:
 #line 769 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 72:
 #line 771 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 73:
 #line 773 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 74:
 #line 775 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 75:
 #line 779 "parse.y"
     {
-                         (yyval.command) = make_for_command ((yyvsp[-4].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_for_command ((yyvsp[(2) - (6)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(5) - (6)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2399,7 +2707,7 @@ yyreduce:
   case 76:
 #line 784 "parse.y"
     {
-                         (yyval.command) = make_for_command ((yyvsp[-4].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_for_command ((yyvsp[(2) - (6)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(5) - (6)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2407,7 +2715,7 @@ yyreduce:
   case 77:
 #line 789 "parse.y"
     {
-                         (yyval.command) = make_for_command ((yyvsp[-5].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_for_command ((yyvsp[(2) - (7)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(6) - (7)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2415,7 +2723,7 @@ yyreduce:
   case 78:
 #line 794 "parse.y"
     {
-                         (yyval.command) = make_for_command ((yyvsp[-5].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_for_command ((yyvsp[(2) - (7)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(6) - (7)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2423,7 +2731,7 @@ yyreduce:
   case 79:
 #line 799 "parse.y"
     {
-                         (yyval.command) = make_for_command ((yyvsp[-8].word), REVERSE_LIST ((yyvsp[-5].word_list), WORD_LIST *), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_for_command ((yyvsp[(2) - (10)].word), REVERSE_LIST ((yyvsp[(5) - (10)].word_list), WORD_LIST *), (yyvsp[(9) - (10)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2431,7 +2739,7 @@ yyreduce:
   case 80:
 #line 804 "parse.y"
     {
-                         (yyval.command) = make_for_command ((yyvsp[-8].word), REVERSE_LIST ((yyvsp[-5].word_list), WORD_LIST *), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_for_command ((yyvsp[(2) - (10)].word), REVERSE_LIST ((yyvsp[(5) - (10)].word_list), WORD_LIST *), (yyvsp[(9) - (10)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2439,7 +2747,7 @@ yyreduce:
   case 81:
 #line 809 "parse.y"
     {
-                         (yyval.command) = make_for_command ((yyvsp[-7].word), (WORD_LIST *)NULL, (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_for_command ((yyvsp[(2) - (9)].word), (WORD_LIST *)NULL, (yyvsp[(8) - (9)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2447,7 +2755,7 @@ yyreduce:
   case 82:
 #line 814 "parse.y"
     {
-                         (yyval.command) = make_for_command ((yyvsp[-7].word), (WORD_LIST *)NULL, (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_for_command ((yyvsp[(2) - (9)].word), (WORD_LIST *)NULL, (yyvsp[(8) - (9)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2455,7 +2763,7 @@ yyreduce:
   case 83:
 #line 821 "parse.y"
     {
-                                 (yyval.command) = make_arith_for_command ((yyvsp[-5].word_list), (yyvsp[-1].command), arith_for_lineno);
+                                 (yyval.command) = make_arith_for_command ((yyvsp[(2) - (7)].word_list), (yyvsp[(6) - (7)].command), arith_for_lineno);
                                  if (word_top > 0) word_top--;
                                }
     break;
@@ -2463,7 +2771,7 @@ yyreduce:
   case 84:
 #line 826 "parse.y"
     {
-                                 (yyval.command) = make_arith_for_command ((yyvsp[-5].word_list), (yyvsp[-1].command), arith_for_lineno);
+                                 (yyval.command) = make_arith_for_command ((yyvsp[(2) - (7)].word_list), (yyvsp[(6) - (7)].command), arith_for_lineno);
                                  if (word_top > 0) word_top--;
                                }
     break;
@@ -2471,7 +2779,7 @@ yyreduce:
   case 85:
 #line 831 "parse.y"
     {
-                                 (yyval.command) = make_arith_for_command ((yyvsp[-3].word_list), (yyvsp[-1].command), arith_for_lineno);
+                                 (yyval.command) = make_arith_for_command ((yyvsp[(2) - (5)].word_list), (yyvsp[(4) - (5)].command), arith_for_lineno);
                                  if (word_top > 0) word_top--;
                                }
     break;
@@ -2479,7 +2787,7 @@ yyreduce:
   case 86:
 #line 836 "parse.y"
     {
-                                 (yyval.command) = make_arith_for_command ((yyvsp[-3].word_list), (yyvsp[-1].command), arith_for_lineno);
+                                 (yyval.command) = make_arith_for_command ((yyvsp[(2) - (5)].word_list), (yyvsp[(4) - (5)].command), arith_for_lineno);
                                  if (word_top > 0) word_top--;
                                }
     break;
@@ -2487,7 +2795,7 @@ yyreduce:
   case 87:
 #line 843 "parse.y"
     {
-                         (yyval.command) = make_select_command ((yyvsp[-4].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_select_command ((yyvsp[(2) - (6)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(5) - (6)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2495,7 +2803,7 @@ yyreduce:
   case 88:
 #line 848 "parse.y"
     {
-                         (yyval.command) = make_select_command ((yyvsp[-4].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_select_command ((yyvsp[(2) - (6)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(5) - (6)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2503,7 +2811,7 @@ yyreduce:
   case 89:
 #line 853 "parse.y"
     {
-                         (yyval.command) = make_select_command ((yyvsp[-5].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_select_command ((yyvsp[(2) - (7)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(6) - (7)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2511,7 +2819,7 @@ yyreduce:
   case 90:
 #line 858 "parse.y"
     {
-                         (yyval.command) = make_select_command ((yyvsp[-5].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_select_command ((yyvsp[(2) - (7)].word), add_string_to_list ("\"$@\"", (WORD_LIST *)NULL), (yyvsp[(6) - (7)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2519,7 +2827,7 @@ yyreduce:
   case 91:
 #line 863 "parse.y"
     {
-                         (yyval.command) = make_select_command ((yyvsp[-8].word), REVERSE_LIST ((yyvsp[-5].word_list), WORD_LIST *), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_select_command ((yyvsp[(2) - (10)].word), REVERSE_LIST ((yyvsp[(5) - (10)].word_list), WORD_LIST *), (yyvsp[(9) - (10)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2527,7 +2835,7 @@ yyreduce:
   case 92:
 #line 868 "parse.y"
     {
-                         (yyval.command) = make_select_command ((yyvsp[-8].word), REVERSE_LIST ((yyvsp[-5].word_list), WORD_LIST *), (yyvsp[-1].command), word_lineno[word_top]);
+                         (yyval.command) = make_select_command ((yyvsp[(2) - (10)].word), REVERSE_LIST ((yyvsp[(5) - (10)].word_list), WORD_LIST *), (yyvsp[(9) - (10)].command), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2535,7 +2843,7 @@ yyreduce:
   case 93:
 #line 875 "parse.y"
     {
-                         (yyval.command) = make_case_command ((yyvsp[-4].word), (PATTERN_LIST *)NULL, word_lineno[word_top]);
+                         (yyval.command) = make_case_command ((yyvsp[(2) - (6)].word), (PATTERN_LIST *)NULL, word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2543,7 +2851,7 @@ yyreduce:
   case 94:
 #line 880 "parse.y"
     {
-                         (yyval.command) = make_case_command ((yyvsp[-5].word), (yyvsp[-2].pattern), word_lineno[word_top]);
+                         (yyval.command) = make_case_command ((yyvsp[(2) - (7)].word), (yyvsp[(5) - (7)].pattern), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
@@ -2551,29 +2859,29 @@ yyreduce:
   case 95:
 #line 885 "parse.y"
     {
-                         (yyval.command) = make_case_command ((yyvsp[-4].word), (yyvsp[-1].pattern), word_lineno[word_top]);
+                         (yyval.command) = make_case_command ((yyvsp[(2) - (6)].word), (yyvsp[(5) - (6)].pattern), word_lineno[word_top]);
                          if (word_top > 0) word_top--;
                        }
     break;
 
   case 96:
 #line 892 "parse.y"
-    { (yyval.command) = make_function_def ((yyvsp[-4].word), (yyvsp[0].command), function_dstart, function_bstart); }
+    { (yyval.command) = make_function_def ((yyvsp[(1) - (5)].word), (yyvsp[(5) - (5)].command), function_dstart, function_bstart); }
     break;
 
   case 97:
 #line 895 "parse.y"
-    { (yyval.command) = make_function_def ((yyvsp[-4].word), (yyvsp[0].command), function_dstart, function_bstart); }
+    { (yyval.command) = make_function_def ((yyvsp[(2) - (6)].word), (yyvsp[(6) - (6)].command), function_dstart, function_bstart); }
     break;
 
   case 98:
 #line 898 "parse.y"
-    { (yyval.command) = make_function_def ((yyvsp[-2].word), (yyvsp[0].command), function_dstart, function_bstart); }
+    { (yyval.command) = make_function_def ((yyvsp[(2) - (4)].word), (yyvsp[(4) - (4)].command), function_dstart, function_bstart); }
     break;
 
   case 99:
 #line 902 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 100:
@@ -2581,7 +2889,7 @@ yyreduce:
     {
                          COMMAND *tc;
 
-                         tc = (yyvsp[-1].command);
+                         tc = (yyvsp[(1) - (2)].command);
                          /* According to Posix.2 3.9.5, redirections
                             specified after the body of a function should
                             be attached to the function and performed when
@@ -2600,18 +2908,18 @@ yyreduce:
                              register REDIRECT *t;
                              for (t = tc->redirects; t->next; t = t->next)
                                ;
-                             t->next = (yyvsp[0].redirect);
+                             t->next = (yyvsp[(2) - (2)].redirect);
                            }
                          else
-                           tc->redirects = (yyvsp[0].redirect);
-                         (yyval.command) = (yyvsp[-1].command);
+                           tc->redirects = (yyvsp[(2) - (2)].redirect);
+                         (yyval.command) = (yyvsp[(1) - (2)].command);
                        }
     break;
 
   case 101:
 #line 935 "parse.y"
     {
-                         (yyval.command) = make_subshell_command ((yyvsp[-1].command));
+                         (yyval.command) = make_subshell_command ((yyvsp[(2) - (3)].command));
                          (yyval.command)->flags |= CMD_WANT_SUBSHELL;
                        }
     break;
@@ -2619,7 +2927,7 @@ yyreduce:
   case 102:
 #line 942 "parse.y"
     {
-                         (yyval.command) = make_coproc_command ("COPROC", (yyvsp[0].command));
+                         (yyval.command) = make_coproc_command ("COPROC", (yyvsp[(2) - (2)].command));
                          (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
                        }
     break;
@@ -2629,17 +2937,17 @@ yyreduce:
     {
                          COMMAND *tc;
 
-                         tc = (yyvsp[-1].command);
+                         tc = (yyvsp[(2) - (3)].command);
                          if (tc->redirects)
                            {
                              register REDIRECT *t;
                              for (t = tc->redirects; t->next; t = t->next)
                                ;
-                             t->next = (yyvsp[0].redirect);
+                             t->next = (yyvsp[(3) - (3)].redirect);
                            }
                          else
-                           tc->redirects = (yyvsp[0].redirect);
-                         (yyval.command) = make_coproc_command ("COPROC", (yyvsp[-1].command));
+                           tc->redirects = (yyvsp[(3) - (3)].redirect);
+                         (yyval.command) = make_coproc_command ("COPROC", (yyvsp[(2) - (3)].command));
                          (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
                        }
     break;
@@ -2647,7 +2955,7 @@ yyreduce:
   case 104:
 #line 964 "parse.y"
     {
-                         (yyval.command) = make_coproc_command ((yyvsp[-1].word)->word, (yyvsp[0].command));
+                         (yyval.command) = make_coproc_command ((yyvsp[(2) - (3)].word)->word, (yyvsp[(3) - (3)].command));
                          (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
                        }
     break;
@@ -2657,17 +2965,17 @@ yyreduce:
     {
                          COMMAND *tc;
 
-                         tc = (yyvsp[-1].command);
+                         tc = (yyvsp[(3) - (4)].command);
                          if (tc->redirects)
                            {
                              register REDIRECT *t;
                              for (t = tc->redirects; t->next; t = t->next)
                                ;
-                             t->next = (yyvsp[0].redirect);
+                             t->next = (yyvsp[(4) - (4)].redirect);
                            }
                          else
-                           tc->redirects = (yyvsp[0].redirect);
-                         (yyval.command) = make_coproc_command ((yyvsp[-2].word)->word, (yyvsp[-1].command));
+                           tc->redirects = (yyvsp[(4) - (4)].redirect);
+                         (yyval.command) = make_coproc_command ((yyvsp[(2) - (4)].word)->word, (yyvsp[(3) - (4)].command));
                          (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
                        }
     break;
@@ -2675,125 +2983,125 @@ yyreduce:
   case 106:
 #line 986 "parse.y"
     {
-                         (yyval.command) = make_coproc_command ("COPROC", clean_simple_command ((yyvsp[0].command)));
+                         (yyval.command) = make_coproc_command ("COPROC", clean_simple_command ((yyvsp[(2) - (2)].command)));
                          (yyval.command)->flags |= CMD_WANT_SUBSHELL|CMD_COPROC_SUBSHELL;
                        }
     break;
 
   case 107:
 #line 993 "parse.y"
-    { (yyval.command) = make_if_command ((yyvsp[-3].command), (yyvsp[-1].command), (COMMAND *)NULL); }
+    { (yyval.command) = make_if_command ((yyvsp[(2) - (5)].command), (yyvsp[(4) - (5)].command), (COMMAND *)NULL); }
     break;
 
   case 108:
 #line 995 "parse.y"
-    { (yyval.command) = make_if_command ((yyvsp[-5].command), (yyvsp[-3].command), (yyvsp[-1].command)); }
+    { (yyval.command) = make_if_command ((yyvsp[(2) - (7)].command), (yyvsp[(4) - (7)].command), (yyvsp[(6) - (7)].command)); }
     break;
 
   case 109:
 #line 997 "parse.y"
-    { (yyval.command) = make_if_command ((yyvsp[-4].command), (yyvsp[-2].command), (yyvsp[-1].command)); }
+    { (yyval.command) = make_if_command ((yyvsp[(2) - (6)].command), (yyvsp[(4) - (6)].command), (yyvsp[(5) - (6)].command)); }
     break;
 
   case 110:
 #line 1002 "parse.y"
-    { (yyval.command) = make_group_command ((yyvsp[-1].command)); }
+    { (yyval.command) = make_group_command ((yyvsp[(2) - (3)].command)); }
     break;
 
   case 111:
 #line 1006 "parse.y"
-    { (yyval.command) = make_arith_command ((yyvsp[0].word_list)); }
+    { (yyval.command) = make_arith_command ((yyvsp[(1) - (1)].word_list)); }
     break;
 
   case 112:
 #line 1010 "parse.y"
-    { (yyval.command) = (yyvsp[-1].command); }
+    { (yyval.command) = (yyvsp[(2) - (3)].command); }
     break;
 
   case 113:
 #line 1014 "parse.y"
-    { (yyval.command) = make_if_command ((yyvsp[-2].command), (yyvsp[0].command), (COMMAND *)NULL); }
+    { (yyval.command) = make_if_command ((yyvsp[(2) - (4)].command), (yyvsp[(4) - (4)].command), (COMMAND *)NULL); }
     break;
 
   case 114:
 #line 1016 "parse.y"
-    { (yyval.command) = make_if_command ((yyvsp[-4].command), (yyvsp[-2].command), (yyvsp[0].command)); }
+    { (yyval.command) = make_if_command ((yyvsp[(2) - (6)].command), (yyvsp[(4) - (6)].command), (yyvsp[(6) - (6)].command)); }
     break;
 
   case 115:
 #line 1018 "parse.y"
-    { (yyval.command) = make_if_command ((yyvsp[-3].command), (yyvsp[-1].command), (yyvsp[0].command)); }
+    { (yyval.command) = make_if_command ((yyvsp[(2) - (5)].command), (yyvsp[(4) - (5)].command), (yyvsp[(5) - (5)].command)); }
     break;
 
   case 117:
 #line 1023 "parse.y"
-    { (yyvsp[0].pattern)->next = (yyvsp[-1].pattern); (yyval.pattern) = (yyvsp[0].pattern); }
+    { (yyvsp[(2) - (2)].pattern)->next = (yyvsp[(1) - (2)].pattern); (yyval.pattern) = (yyvsp[(2) - (2)].pattern); }
     break;
 
   case 118:
 #line 1027 "parse.y"
-    { (yyval.pattern) = make_pattern_list ((yyvsp[-2].word_list), (yyvsp[0].command)); }
+    { (yyval.pattern) = make_pattern_list ((yyvsp[(2) - (4)].word_list), (yyvsp[(4) - (4)].command)); }
     break;
 
   case 119:
 #line 1029 "parse.y"
-    { (yyval.pattern) = make_pattern_list ((yyvsp[-2].word_list), (COMMAND *)NULL); }
+    { (yyval.pattern) = make_pattern_list ((yyvsp[(2) - (4)].word_list), (COMMAND *)NULL); }
     break;
 
   case 120:
 #line 1031 "parse.y"
-    { (yyval.pattern) = make_pattern_list ((yyvsp[-2].word_list), (yyvsp[0].command)); }
+    { (yyval.pattern) = make_pattern_list ((yyvsp[(3) - (5)].word_list), (yyvsp[(5) - (5)].command)); }
     break;
 
   case 121:
 #line 1033 "parse.y"
-    { (yyval.pattern) = make_pattern_list ((yyvsp[-2].word_list), (COMMAND *)NULL); }
+    { (yyval.pattern) = make_pattern_list ((yyvsp[(3) - (5)].word_list), (COMMAND *)NULL); }
     break;
 
   case 122:
 #line 1037 "parse.y"
-    { (yyval.pattern) = (yyvsp[-1].pattern); }
+    { (yyval.pattern) = (yyvsp[(1) - (2)].pattern); }
     break;
 
   case 123:
 #line 1039 "parse.y"
-    { (yyvsp[-1].pattern)->next = (yyvsp[-2].pattern); (yyval.pattern) = (yyvsp[-1].pattern); }
+    { (yyvsp[(2) - (3)].pattern)->next = (yyvsp[(1) - (3)].pattern); (yyval.pattern) = (yyvsp[(2) - (3)].pattern); }
     break;
 
   case 124:
 #line 1041 "parse.y"
-    { (yyvsp[-1].pattern)->flags |= CASEPAT_FALLTHROUGH; (yyval.pattern) = (yyvsp[-1].pattern); }
+    { (yyvsp[(1) - (2)].pattern)->flags |= CASEPAT_FALLTHROUGH; (yyval.pattern) = (yyvsp[(1) - (2)].pattern); }
     break;
 
   case 125:
 #line 1043 "parse.y"
-    { (yyvsp[-1].pattern)->flags |= CASEPAT_FALLTHROUGH; (yyvsp[-1].pattern)->next = (yyvsp[-2].pattern); (yyval.pattern) = (yyvsp[-1].pattern); }
+    { (yyvsp[(2) - (3)].pattern)->flags |= CASEPAT_FALLTHROUGH; (yyvsp[(2) - (3)].pattern)->next = (yyvsp[(1) - (3)].pattern); (yyval.pattern) = (yyvsp[(2) - (3)].pattern); }
     break;
 
   case 126:
 #line 1045 "parse.y"
-    { (yyvsp[-1].pattern)->flags |= CASEPAT_TESTNEXT; (yyval.pattern) = (yyvsp[-1].pattern); }
+    { (yyvsp[(1) - (2)].pattern)->flags |= CASEPAT_TESTNEXT; (yyval.pattern) = (yyvsp[(1) - (2)].pattern); }
     break;
 
   case 127:
 #line 1047 "parse.y"
-    { (yyvsp[-1].pattern)->flags |= CASEPAT_TESTNEXT; (yyvsp[-1].pattern)->next = (yyvsp[-2].pattern); (yyval.pattern) = (yyvsp[-1].pattern); }
+    { (yyvsp[(2) - (3)].pattern)->flags |= CASEPAT_TESTNEXT; (yyvsp[(2) - (3)].pattern)->next = (yyvsp[(1) - (3)].pattern); (yyval.pattern) = (yyvsp[(2) - (3)].pattern); }
     break;
 
   case 128:
 #line 1051 "parse.y"
-    { (yyval.word_list) = make_word_list ((yyvsp[0].word), (WORD_LIST *)NULL); }
+    { (yyval.word_list) = make_word_list ((yyvsp[(1) - (1)].word), (WORD_LIST *)NULL); }
     break;
 
   case 129:
 #line 1053 "parse.y"
-    { (yyval.word_list) = make_word_list ((yyvsp[0].word), (yyvsp[-2].word_list)); }
+    { (yyval.word_list) = make_word_list ((yyvsp[(3) - (3)].word), (yyvsp[(1) - (3)].word_list)); }
     break;
 
   case 130:
 #line 1062 "parse.y"
     {
-                         (yyval.command) = (yyvsp[0].command);
+                         (yyval.command) = (yyvsp[(2) - (2)].command);
                          if (need_here_doc)
                            gather_here_documents ();
                         }
@@ -2802,53 +3110,53 @@ yyreduce:
   case 132:
 #line 1071 "parse.y"
     {
-                         (yyval.command) = (yyvsp[0].command);
+                         (yyval.command) = (yyvsp[(2) - (2)].command);
                        }
     break;
 
   case 134:
 #line 1078 "parse.y"
     {
-                         if ((yyvsp[-2].command)->type == cm_connection)
-                           (yyval.command) = connect_async_list ((yyvsp[-2].command), (COMMAND *)NULL, '&');
+                         if ((yyvsp[(1) - (3)].command)->type == cm_connection)
+                           (yyval.command) = connect_async_list ((yyvsp[(1) - (3)].command), (COMMAND *)NULL, '&');
                          else
-                           (yyval.command) = command_connect ((yyvsp[-2].command), (COMMAND *)NULL, '&');
+                           (yyval.command) = command_connect ((yyvsp[(1) - (3)].command), (COMMAND *)NULL, '&');
                        }
     break;
 
   case 136:
 #line 1089 "parse.y"
-    { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), AND_AND); }
+    { (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), AND_AND); }
     break;
 
   case 137:
 #line 1091 "parse.y"
-    { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), OR_OR); }
+    { (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), OR_OR); }
     break;
 
   case 138:
 #line 1093 "parse.y"
     {
-                         if ((yyvsp[-3].command)->type == cm_connection)
-                           (yyval.command) = connect_async_list ((yyvsp[-3].command), (yyvsp[0].command), '&');
+                         if ((yyvsp[(1) - (4)].command)->type == cm_connection)
+                           (yyval.command) = connect_async_list ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), '&');
                          else
-                           (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), '&');
+                           (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), '&');
                        }
     break;
 
   case 139:
 #line 1100 "parse.y"
-    { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), ';'); }
+    { (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), ';'); }
     break;
 
   case 140:
 #line 1102 "parse.y"
-    { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), ';'); }
+    { (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), ';'); }
     break;
 
   case 141:
 #line 1104 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 144:
@@ -2869,12 +3177,12 @@ yyreduce:
   case 149:
 #line 1130 "parse.y"
     {
-                         (yyval.command) = (yyvsp[0].command);
+                         (yyval.command) = (yyvsp[(1) - (1)].command);
                          if (need_here_doc)
                            gather_here_documents ();
                          if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
                            {
-                             global_command = (yyvsp[0].command);
+                             global_command = (yyvsp[(1) - (1)].command);
                              eof_encountered = 0;
                              rewind_input_string ();
                              YYACCEPT;
@@ -2885,15 +3193,15 @@ yyreduce:
   case 150:
 #line 1143 "parse.y"
     {
-                         if ((yyvsp[-1].command)->type == cm_connection)
-                           (yyval.command) = connect_async_list ((yyvsp[-1].command), (COMMAND *)NULL, '&');
+                         if ((yyvsp[(1) - (2)].command)->type == cm_connection)
+                           (yyval.command) = connect_async_list ((yyvsp[(1) - (2)].command), (COMMAND *)NULL, '&');
                          else
-                           (yyval.command) = command_connect ((yyvsp[-1].command), (COMMAND *)NULL, '&');
+                           (yyval.command) = command_connect ((yyvsp[(1) - (2)].command), (COMMAND *)NULL, '&');
                          if (need_here_doc)
                            gather_here_documents ();
                          if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
                            {
-                             global_command = (yyvsp[-1].command);
+                             global_command = (yyvsp[(1) - (2)].command);
                              eof_encountered = 0;
                              rewind_input_string ();
                              YYACCEPT;
@@ -2904,12 +3212,12 @@ yyreduce:
   case 151:
 #line 1159 "parse.y"
     {
-                         (yyval.command) = (yyvsp[-1].command);
+                         (yyval.command) = (yyvsp[(1) - (2)].command);
                          if (need_here_doc)
                            gather_here_documents ();
                          if ((parser_state & PST_CMDSUBST) && current_token == shell_eof_token)
                            {
-                             global_command = (yyvsp[-1].command);
+                             global_command = (yyvsp[(1) - (2)].command);
                              eof_encountered = 0;
                              rewind_input_string ();
                              YYACCEPT;
@@ -2919,54 +3227,54 @@ yyreduce:
 
   case 152:
 #line 1174 "parse.y"
-    { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), AND_AND); }
+    { (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), AND_AND); }
     break;
 
   case 153:
 #line 1176 "parse.y"
-    { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), OR_OR); }
+    { (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), OR_OR); }
     break;
 
   case 154:
 #line 1178 "parse.y"
     {
-                         if ((yyvsp[-2].command)->type == cm_connection)
-                           (yyval.command) = connect_async_list ((yyvsp[-2].command), (yyvsp[0].command), '&');
+                         if ((yyvsp[(1) - (3)].command)->type == cm_connection)
+                           (yyval.command) = connect_async_list ((yyvsp[(1) - (3)].command), (yyvsp[(3) - (3)].command), '&');
                          else
-                           (yyval.command) = command_connect ((yyvsp[-2].command), (yyvsp[0].command), '&');
+                           (yyval.command) = command_connect ((yyvsp[(1) - (3)].command), (yyvsp[(3) - (3)].command), '&');
                        }
     break;
 
   case 155:
 #line 1185 "parse.y"
-    { (yyval.command) = command_connect ((yyvsp[-2].command), (yyvsp[0].command), ';'); }
+    { (yyval.command) = command_connect ((yyvsp[(1) - (3)].command), (yyvsp[(3) - (3)].command), ';'); }
     break;
 
   case 156:
 #line 1188 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 157:
 #line 1192 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 158:
 #line 1194 "parse.y"
     {
-                         if ((yyvsp[0].command))
-                           (yyvsp[0].command)->flags ^= CMD_INVERT_RETURN;     /* toggle */
-                         (yyval.command) = (yyvsp[0].command);
+                         if ((yyvsp[(2) - (2)].command))
+                           (yyvsp[(2) - (2)].command)->flags ^= CMD_INVERT_RETURN;     /* toggle */
+                         (yyval.command) = (yyvsp[(2) - (2)].command);
                        }
     break;
 
   case 159:
 #line 1200 "parse.y"
     {
-                         if ((yyvsp[0].command))
-                           (yyvsp[0].command)->flags |= (yyvsp[-1].number);
-                         (yyval.command) = (yyvsp[0].command);
+                         if ((yyvsp[(2) - (2)].command))
+                           (yyvsp[(2) - (2)].command)->flags |= (yyvsp[(1) - (2)].number);
+                         (yyval.command) = (yyvsp[(2) - (2)].command);
                        }
     break;
 
@@ -2983,9 +3291,9 @@ yyreduce:
                          x.word = 0;
                          x.redirect = 0;
                          (yyval.command) = make_simple_command (x, (COMMAND *)NULL);
-                         (yyval.command)->flags |= (yyvsp[-1].number);
+                         (yyval.command)->flags |= (yyvsp[(1) - (2)].number);
                          /* XXX - let's cheat and push a newline back */
-                         if ((yyvsp[0].number) == '\n')
+                         if ((yyvsp[(2) - (2)].number) == '\n')
                            token_to_read = '\n';
                        }
     break;
@@ -3006,14 +3314,14 @@ yyreduce:
                          (yyval.command) = make_simple_command (x, (COMMAND *)NULL);
                          (yyval.command)->flags |= CMD_INVERT_RETURN;
                          /* XXX - let's cheat and push a newline back */
-                         if ((yyvsp[0].number) == '\n')
+                         if ((yyvsp[(2) - (2)].number) == '\n')
                            token_to_read = '\n';
                        }
     break;
 
   case 162:
 #line 1243 "parse.y"
-    { (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), '|'); }
+    { (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), '|'); }
     break;
 
   case 163:
@@ -3024,7 +3332,7 @@ yyreduce:
                          REDIRECTEE rd, sd;
                          REDIRECT *r;
 
-                         tc = (yyvsp[-3].command)->type == cm_simple ? (COMMAND *)(yyvsp[-3].command)->value.Simple : (yyvsp[-3].command);
+                         tc = (yyvsp[(1) - (4)].command)->type == cm_simple ? (COMMAND *)(yyvsp[(1) - (4)].command)->value.Simple : (yyvsp[(1) - (4)].command);
                          sd.dest = 2;
                          rd.dest = 1;
                          r = make_redirection (sd, r_duplicating_output, rd, 0);
@@ -3038,13 +3346,13 @@ yyreduce:
                          else
                            tc->redirects = r;
 
-                         (yyval.command) = command_connect ((yyvsp[-3].command), (yyvsp[0].command), '|');
+                         (yyval.command) = command_connect ((yyvsp[(1) - (4)].command), (yyvsp[(4) - (4)].command), '|');
                        }
     break;
 
   case 164:
 #line 1268 "parse.y"
-    { (yyval.command) = (yyvsp[0].command); }
+    { (yyval.command) = (yyvsp[(1) - (1)].command); }
     break;
 
   case 165:
@@ -3063,15 +3371,14 @@ yyreduce:
     break;
 
 
+/* Line 1267 of yacc.c.  */
+#line 3376 "y.tab.c"
+      default: break;
     }
+  YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
 
-/* Line 1037 of yacc.c.  */
-#line 3070 "y.tab.c"
-\f
-  yyvsp -= yylen;
-  yyssp -= yylen;
-
-
+  YYPOPSTACK (yylen);
+  yylen = 0;
   YY_STACK_PRINT (yyss, yyssp);
 
   *++yyvsp = yyval;
@@ -3100,66 +3407,41 @@ yyerrlab:
   if (!yyerrstatus)
     {
       ++yynerrs;
-#if YYERROR_VERBOSE
-      yyn = yypact[yystate];
-
-      if (YYPACT_NINF < yyn && yyn < YYLAST)
-       {
-         YYSIZE_T yysize = 0;
-         int yytype = YYTRANSLATE (yychar);
-         const char* yyprefix;
-         char *yymsg;
-         int yyx;
-
-         /* Start YYX at -YYN if negative to avoid negative indexes in
-            YYCHECK.  */
-         int yyxbegin = yyn < 0 ? -yyn : 0;
-
-         /* Stay within bounds of both yycheck and yytname.  */
-         int yychecklim = YYLAST - yyn;
-         int yyxend = yychecklim < YYNTOKENS ? yychecklim : YYNTOKENS;
-         int yycount = 0;
-
-         yyprefix = ", expecting ";
-         for (yyx = yyxbegin; yyx < yyxend; ++yyx)
-           if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR)
+#if ! YYERROR_VERBOSE
+      yyerror (YY_("syntax error"));
+#else
+      {
+       YYSIZE_T yysize = yysyntax_error (0, yystate, yychar);
+       if (yymsg_alloc < yysize && yymsg_alloc < YYSTACK_ALLOC_MAXIMUM)
+         {
+           YYSIZE_T yyalloc = 2 * yysize;
+           if (! (yysize <= yyalloc && yyalloc <= YYSTACK_ALLOC_MAXIMUM))
+             yyalloc = YYSTACK_ALLOC_MAXIMUM;
+           if (yymsg != yymsgbuf)
+             YYSTACK_FREE (yymsg);
+           yymsg = (char *) YYSTACK_ALLOC (yyalloc);
+           if (yymsg)
+             yymsg_alloc = yyalloc;
+           else
              {
-               yysize += yystrlen (yyprefix) + yystrlen (yytname [yyx]);
-               yycount += 1;
-               if (yycount == 5)
-                 {
-                   yysize = 0;
-                   break;
-                 }
+               yymsg = yymsgbuf;
+               yymsg_alloc = sizeof yymsgbuf;
              }
-         yysize += (sizeof ("syntax error, unexpected ")
-                    + yystrlen (yytname[yytype]));
-         yymsg = (char *) YYSTACK_ALLOC (yysize);
-         if (yymsg != 0)
-           {
-             char *yyp = yystpcpy (yymsg, "syntax error, unexpected ");
-             yyp = yystpcpy (yyp, yytname[yytype]);
+         }
 
-             if (yycount < 5)
-               {
-                 yyprefix = ", expecting ";
-                 for (yyx = yyxbegin; yyx < yyxend; ++yyx)
-                   if (yycheck[yyx + yyn] == yyx && yyx != YYTERROR)
-                     {
-                       yyp = yystpcpy (yyp, yyprefix);
-                       yyp = yystpcpy (yyp, yytname[yyx]);
-                       yyprefix = " or ";
-                     }
-               }
-             yyerror (yymsg);
-             YYSTACK_FREE (yymsg);
-           }
-         else
-           yyerror ("syntax error; also virtual memory exhausted");
-       }
-      else
-#endif /* YYERROR_VERBOSE */
-       yyerror ("syntax error");
+       if (0 < yysize && yysize <= yymsg_alloc)
+         {
+           (void) yysyntax_error (yymsg, yystate, yychar);
+           yyerror (yymsg);
+         }
+       else
+         {
+           yyerror (YY_("syntax error"));
+           if (yysize != 0)
+             goto yyexhaustedlab;
+         }
+      }
+#endif
     }
 
 
@@ -3170,23 +3452,15 @@ yyerrlab:
         error, discard it.  */
 
       if (yychar <= YYEOF)
-        {
-          /* If at end of input, pop the error token,
-            then the rest of the stack, then return failure.  */
+       {
+         /* Return failure if at end of input.  */
          if (yychar == YYEOF)
-            for (;;)
-              {
-
-                YYPOPSTACK;
-                if (yyssp == yyss)
-                  YYABORT;
-                yydestruct ("Error: popping",
-                             yystos[*yyssp], yyvsp);
-              }
-        }
+           YYABORT;
+       }
       else
        {
-         yydestruct ("Error: discarding", yytoken, &yylval);
+         yydestruct ("Error: discarding",
+                     yytoken, &yylval);
          yychar = YYEMPTY;
        }
     }
@@ -3201,15 +3475,17 @@ yyerrlab:
 `---------------------------------------------------*/
 yyerrorlab:
 
-#ifdef __GNUC__
-  /* Pacify GCC when the user code never invokes YYERROR and the label
-     yyerrorlab therefore never appears in user code.  */
-  if (0)
+  /* Pacify compilers like GCC when the user code never invokes
+     YYERROR and the label yyerrorlab therefore never appears in user
+     code.  */
+  if (/*CONSTCOND*/ 0)
      goto yyerrorlab;
-#endif
 
-yyvsp -= yylen;
-  yyssp -= yylen;
+  /* Do not reclaim the symbols of the rule which action triggered
+     this YYERROR.  */
+  YYPOPSTACK (yylen);
+  yylen = 0;
+  YY_STACK_PRINT (yyss, yyssp);
   yystate = *yyssp;
   goto yyerrlab1;
 
@@ -3239,8 +3515,9 @@ yyerrlab1:
        YYABORT;
 
 
-      yydestruct ("Error: popping", yystos[yystate], yyvsp);
-      YYPOPSTACK;
+      yydestruct ("Error: popping",
+                 yystos[yystate], yyvsp);
+      YYPOPSTACK (1);
       yystate = *yyssp;
       YY_STACK_PRINT (yyss, yyssp);
     }
@@ -3251,7 +3528,7 @@ yyerrlab1:
   *++yyvsp = yylval;
 
 
-  /* Shift the error token. */
+  /* Shift the error token.  */
   YY_SYMBOL_PRINT ("Shifting", yystos[yyn], yyvsp, yylsp);
 
   yystate = yyn;
@@ -3269,28 +3546,43 @@ yyacceptlab:
 | yyabortlab -- YYABORT comes here.  |
 `-----------------------------------*/
 yyabortlab:
-  yydestruct ("Error: discarding lookahead",
-              yytoken, &yylval);
-  yychar = YYEMPTY;
   yyresult = 1;
   goto yyreturn;
 
 #ifndef yyoverflow
-/*----------------------------------------------.
-| yyoverflowlab -- parser overflow comes here.  |
-`----------------------------------------------*/
-yyoverflowlab:
-  yyerror ("parser stack overflow");
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here.  |
+`-------------------------------------------------*/
+yyexhaustedlab:
+  yyerror (YY_("memory exhausted"));
   yyresult = 2;
   /* Fall through.  */
 #endif
 
 yyreturn:
+  if (yychar != YYEOF && yychar != YYEMPTY)
+     yydestruct ("Cleanup: discarding lookahead",
+                yytoken, &yylval);
+  /* Do not reclaim the symbols of the rule which action triggered
+     this YYABORT or YYACCEPT.  */
+  YYPOPSTACK (yylen);
+  YY_STACK_PRINT (yyss, yyssp);
+  while (yyssp != yyss)
+    {
+      yydestruct ("Cleanup: popping",
+                 yystos[*yyssp], yyvsp);
+      YYPOPSTACK (1);
+    }
 #ifndef yyoverflow
   if (yyss != yyssa)
     YYSTACK_FREE (yyss);
 #endif
-  return yyresult;
+#if YYERROR_VERBOSE
+  if (yymsg != yymsgbuf)
+    YYSTACK_FREE (yymsg);
+#endif
+  /* Make sure YYID is used.  */
+  return YYID (yyresult);
 }
 
 
diff --git a/y.tab.h b/y.tab.h
index b347c60ff7205e4e09f33e92b1da7ea48c21e836..72d2fd70c5a0ef6cf20ffe7958afaacc18486996 100644 (file)
--- a/y.tab.h
+++ b/y.tab.h
@@ -1,7 +1,9 @@
-/* A Bison parser, made by GNU Bison 2.0.  */
+/* A Bison parser, made by GNU Bison 2.3.  */
 
-/* Skeleton parser for Yacc-like parsing with Bison,
-   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
+/* Skeleton interface for Bison's Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989, 1990, 2000, 2001, 2002, 2003, 2004, 2005, 2006
+   Free Software Foundation, Inc.
 
    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
 
    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.  */
+   Foundation, Inc., 51 Franklin Street, Fifth Floor,
+   Boston, MA 02110-1301, USA.  */
+
+/* As a special exception, you may create a larger work that contains
+   part or all of the Bison parser skeleton and distribute that work
+   under terms of your choice, so long as that work isn't itself a
+   parser generator using the skeleton or a modified version thereof
+   as a parser skeleton.  Alternatively, if you modify or redistribute
+   the parser skeleton itself, you may (at your option) remove this
+   special exception, which will cause the skeleton and the resulting
+   Bison output files to be licensed under the GNU General Public
+   License without this special exception.
 
-/* As a special exception, when this file is copied by Bison into a
-   Bison output file, you may use that output file without restriction.
-   This special exception was added by the Free Software Foundation
-   in version 1.24 of Bison.  */
+   This special exception was added by the Free Software Foundation in
+   version 2.2 of Bison.  */
 
 /* Tokens.  */
 #ifndef YYTOKENTYPE
@@ -78,6 +88,7 @@
      yacc_EOF = 304
    };
 #endif
+/* Tokens.  */
 #define IF 258
 #define THEN 259
 #define ELSE 260
 
 
 
-#if ! defined (YYSTYPE) && ! defined (YYSTYPE_IS_DECLARED)
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+typedef union YYSTYPE
 #line 323 "parse.y"
-typedef union YYSTYPE {
+{
   WORD_DESC *word;             /* the word that we read. */
   int number;                  /* the number that we read. */
   WORD_LIST *word_list;
@@ -139,9 +151,10 @@ typedef union YYSTYPE {
   REDIRECT *redirect;
   ELEMENT element;
   PATTERN_LIST *pattern;
-} YYSTYPE;
-/* Line 1274 of yacc.c.  */
-#line 145 "y.tab.h"
+}
+/* Line 1489 of yacc.c.  */
+#line 157 "y.tab.h"
+       YYSTYPE;
 # define yystype YYSTYPE /* obsolescent; will be withdrawn */
 # define YYSTYPE_IS_DECLARED 1
 # define YYSTYPE_IS_TRIVIAL 1
@@ -149,5 +162,3 @@ typedef union YYSTYPE {
 
 extern YYSTYPE yylval;
 
-
-