bring back ${ foo;} sans dot.mkshrc patch, using a temporary file, and as experimental feature

This commit is contained in:
tg
2012-10-22 20:19:18 +00:00
parent 574c024635
commit bfe7d78d40
9 changed files with 177 additions and 37 deletions

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.588 2012/10/22 16:53:20 tg Exp $' srcversion='$MirOS: src/bin/mksh/Build.sh,v 1.589 2012/10/22 20:19:08 tg Exp $'
#- #-
# Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, # Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
# 2011, 2012 # 2011, 2012
@ -1370,6 +1370,9 @@ ac_ifcpp 'ifdef MKSH_CONSERVATIVE_FDS' isset_MKSH_CONSERVATIVE_FDS '' \
#ac_ifcpp 'ifdef MKSH_DISABLE_DEPRECATED' isset_MKSH_DISABLE_DEPRECATED '' \ #ac_ifcpp 'ifdef MKSH_DISABLE_DEPRECATED' isset_MKSH_DISABLE_DEPRECATED '' \
# "if deprecated features are to be omitted" && \ # "if deprecated features are to be omitted" && \
# check_categories="$check_categories nodeprecated" # check_categories="$check_categories nodeprecated"
ac_ifcpp 'ifdef MKSH_DISABLE_EXPERIMENTAL' isset_MKSH_DISABLE_EXPERIMENTAL '' \
"if experimental features are to be omitted" && \
check_categories="$check_categories noexperimental"
# #
# Environment: headers # Environment: headers
@ -1492,7 +1495,7 @@ else
#define EXTERN #define EXTERN
#define MKSH_INCLUDES_ONLY #define MKSH_INCLUDES_ONLY
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/Build.sh,v 1.588 2012/10/22 16:53:20 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/Build.sh,v 1.589 2012/10/22 20:19:08 tg Exp $");
int main(void) { printf("Hello, World!\n"); return (0); } int main(void) { printf("Hello, World!\n"); return (0); }
EOF EOF
case $cm in case $cm in

27
check.t
View File

@ -1,4 +1,4 @@
# $MirOS: src/bin/mksh/check.t,v 1.561 2012/10/21 21:55:01 tg Exp $ # $MirOS: src/bin/mksh/check.t,v 1.562 2012/10/22 20:19:10 tg Exp $
# $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $
# $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $
# $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ # $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $
@ -29,7 +29,7 @@
# http://www.freebsd.org/cgi/cvsweb.cgi/src/tools/regression/bin/test/regress.sh?rev=HEAD # http://www.freebsd.org/cgi/cvsweb.cgi/src/tools/regression/bin/test/regress.sh?rev=HEAD
expected-stdout: expected-stdout:
@(#)MIRBSD KSH R40 2012/10/21 @(#)MIRBSD KSH R40 2012/10/22
description: description:
Check version of shell. Check version of shell.
stdin: stdin:
@ -38,7 +38,7 @@ name: KSH_VERSION
category: shell:legacy-no category: shell:legacy-no
--- ---
expected-stdout: expected-stdout:
@(#)LEGACY KSH R40 2012/10/21 @(#)LEGACY KSH R40 2012/10/22
description: description:
Check version of legacy shell. Check version of legacy shell.
stdin: stdin:
@ -9553,6 +9553,27 @@ expected-stdout:
x=$(( echo $(true >&3 ) $((1+ 2)) ) | tr u x ) x=$(( echo $(true >&3 ) $((1+ 2)) ) | tr u x )
} }
--- ---
name: funsub-1
description:
Check that non-subenvironment command substitution works
category: !noexperimental
stdin:
set -e
foo=bar
echo "ob $foo ."
echo "${
echo "ib $foo :"
foo=baz
echo "ia $foo :"
false
}" .
echo "oa $foo ."
expected-stdout:
ob bar .
ib bar :
ia baz : .
oa baz .
---
name: test-stnze-1 name: test-stnze-1
description: description:
Check that the short form [ $x ] works Check that the short form [ $x ] works

47
eval.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/eval.c,v 1.128 2012/08/24 21:15:42 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/eval.c,v 1.129 2012/10/22 20:19:12 tg Exp $");
/* /*
* string expansion * string expansion
@ -58,7 +58,7 @@ typedef struct Expand {
#define IFS_NWS 2 /* have seen IFS non-white-space */ #define IFS_NWS 2 /* have seen IFS non-white-space */
static int varsub(Expand *, const char *, const char *, int *, int *); static int varsub(Expand *, const char *, const char *, int *, int *);
static int comsub(Expand *, const char *); static int comsub(Expand *, const char *, int);
static char *trimsub(char *, char *, int); static char *trimsub(char *, char *, int);
static void glob(char *, XPtrV *, bool); static void glob(char *, XPtrV *, bool);
static void globit(XString *, char **, char *, XPtrV *, int); static void globit(XString *, char **, char *, XPtrV *, int);
@ -277,18 +277,33 @@ expand(const char *cp, /* input word */
quote = st->quotew; quote = st->quotew;
continue; continue;
case COMSUB: case COMSUB:
#ifndef MKSH_DISABLE_EXPERIMENTAL
case FUNSUB:
#endif
tilde_ok = 0; tilde_ok = 0;
if (f & DONTRUNCOMMAND) { if (f & DONTRUNCOMMAND) {
word = IFS_WORD; word = IFS_WORD;
*dp++ = '$'; *dp++ = '$';
#ifndef MKSH_DISABLE_EXPERIMENTAL
if (c == FUNSUB) {
*dp++ = '{';
*dp++ = ' ';
} else
#endif
*dp++ = '('; *dp++ = '(';
while (*sp != '\0') { while (*sp != '\0') {
Xcheck(ds, dp); Xcheck(ds, dp);
*dp++ = *sp++; *dp++ = *sp++;
} }
#ifndef MKSH_DISABLE_EXPERIMENTAL
if (c == FUNSUB) {
*dp++ = ';';
*dp++ = '}';
} else
#endif
*dp++ = ')'; *dp++ = ')';
} else { } else {
type = comsub(&x, sp); type = comsub(&x, sp, c);
if (type == XCOM && (f&DOBLANK)) if (type == XCOM && (f&DOBLANK))
doblank++; doblank++;
sp = strnul(sp) + 1; sp = strnul(sp) + 1;
@ -1272,7 +1287,7 @@ varsub(Expand *xp, const char *sp, const char *word,
* Run the command in $(...) and read its output. * Run the command in $(...) and read its output.
*/ */
static int static int
comsub(Expand *xp, const char *cp) comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED)
{ {
Source *s, *sold; Source *s, *sold;
struct op *t; struct op *t;
@ -1307,6 +1322,30 @@ comsub(Expand *xp, const char *cp)
SHF_MAPHI|SHF_CLEXEC); SHF_MAPHI|SHF_CLEXEC);
if (shf == NULL) if (shf == NULL)
errorf("%s: %s %s", name, "can't open", "$() input"); errorf("%s: %s %s", name, "can't open", "$() input");
#ifndef MKSH_DISABLE_EXPERIMENTAL
} else if (fn == FUNSUB) {
int ofd1;
struct temp *tf = NULL;
/* create a temporary file, open for writing */
maketemp(ATEMP, TT_FUNSUB, &tf);
if (!tf->shf) {
errorf("can't %s temporary file %s: %s",
"create", tf->tffn, strerror(errno));
}
/* save stdout and make the temporary file it */
ofd1 = savefd(1);
ksh_dup2(shf_fileno(tf->shf), 1, false);
/* run tree, with output thrown into the tempfile */
execute(t, XXCOM | XERROK, NULL);
/* close the tempfile and restore regular stdout */
shf_close(tf->shf);
restfd(1, ofd1);
/* now open, unlink and free the tempfile for reading */
shf = shf_open(tf->tffn, O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC);
unlink(tf->tffn);
afree(tf, ATEMP);
#endif
} else { } else {
int ofd1, pv[2]; int ofd1, pv[2];

33
lex.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/lex.c,v 1.168 2012/10/03 17:24:20 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/lex.c,v 1.169 2012/10/22 20:19:13 tg Exp $");
/* /*
* states while lexing word * states while lexing word
@ -109,7 +109,7 @@ void yyskiputf8bom(void);
static int backslash_skip; static int backslash_skip;
static int ignore_backslash_newline; static int ignore_backslash_newline;
static struct sretrace_info *retrace_info; static struct sretrace_info *retrace_info;
uint8_t subshell_nesting_level = 0; int subshell_nesting_type = 0;
/* optimised getsc_bn() */ /* optimised getsc_bn() */
#define o_getsc() (*source->str != '\0' && *source->str != '\\' && \ #define o_getsc() (*source->str != '\0' && *source->str != '\\' && \
@ -262,6 +262,13 @@ yylex(int cf)
while (!((c = getsc()) == 0 || while (!((c = getsc()) == 0 ||
((state == SBASE || state == SHEREDELIM || state == SHERESTRING) && ((state == SBASE || state == SHEREDELIM || state == SHERESTRING) &&
ctype(c, C_LEX1)))) { ctype(c, C_LEX1)))) {
#ifndef MKSH_DISABLE_EXPERIMENTAL
if (state == SBASE &&
subshell_nesting_type == /*{*/ '}' &&
c == /*{*/ '}')
/* possibly end ${ :;} */
break;
#endif
accept_nonword: accept_nonword:
Xcheck(ws, wp); Xcheck(ws, wp);
switch (state) { switch (state) {
@ -395,14 +402,30 @@ yylex(int cf)
} else { } else {
ungetsc(c); ungetsc(c);
subst_command: subst_command:
sp = yyrecursive(); c = COMSUB;
#ifndef MKSH_DISABLE_EXPERIMENTAL
subst_command2:
#endif
sp = yyrecursive(c);
cz = strlen(sp) + 1; cz = strlen(sp) + 1;
XcheckN(ws, wp, cz); XcheckN(ws, wp, cz);
*wp++ = COMSUB; *wp++ = c;
memcpy(wp, sp, cz); memcpy(wp, sp, cz);
wp += cz; wp += cz;
} }
} else if (c == '{') /*}*/ { } else if (c == '{') /*}*/ {
#ifndef MKSH_DISABLE_EXPERIMENTAL
c = getsc();
if (ctype(c, C_IFSWS)) {
/*
* non-subenvironment
* "command" substitution
*/
c = FUNSUB;
goto subst_command2;
}
ungetsc(c);
#endif
*wp++ = OSUBST; *wp++ = OSUBST;
*wp++ = '{'; /*}*/ *wp++ = '{'; /*}*/
wp = get_brace_var(&ws, wp); wp = get_brace_var(&ws, wp);
@ -1171,7 +1194,7 @@ readhere(struct ioword *iop)
/* end of here document marker, what to do? */ /* end of here document marker, what to do? */
switch (c) { switch (c) {
case /*(*/ ')': case /*(*/ ')':
if (!subshell_nesting_level) if (!subshell_nesting_type)
/*- /*-
* not allowed outside $(...) or (...) * not allowed outside $(...) or (...)
* => mismatch * => mismatch

16
main.c
View File

@ -34,7 +34,7 @@
#include <locale.h> #include <locale.h>
#endif #endif
__RCSID("$MirOS: src/bin/mksh/main.c,v 1.232 2012/10/22 16:53:22 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/main.c,v 1.233 2012/10/22 20:19:14 tg Exp $");
extern char **environ; extern char **environ;
@ -1377,7 +1377,7 @@ void
restfd(int fd, int ofd) restfd(int fd, int ofd)
{ {
if (fd == 2) if (fd == 2)
shf_flush(&shf_iob[fd]); shf_flush(&shf_iob[/* fd */ 2]);
if (ofd < 0) if (ofd < 0)
/* original fd closed */ /* original fd closed */
close(fd); close(fd);
@ -1591,6 +1591,18 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist)
/* do another cycle */ /* do another cycle */
} }
#ifndef MKSH_DISABLE_EXPERIMENTAL
if (type == TT_FUNSUB) {
int nfd;
/* map us high and mark as close-on-exec */
if ((nfd = savefd(i)) != i) {
close(i);
i = nfd;
}
}
#endif
/* shf_fdopen cannot fail, so no fd leak */ /* shf_fdopen cannot fail, so no fd leak */
tp->shf = shf_fdopen(i, SHF_WR, NULL); tp->shf = shf_fdopen(i, SHF_WR, NULL);

11
mksh.1
View File

@ -1,4 +1,4 @@
.\" $MirOS: src/bin/mksh/mksh.1,v 1.295 2012/10/21 17:42:51 tg Exp $ .\" $MirOS: src/bin/mksh/mksh.1,v 1.296 2012/10/22 20:19:14 tg Exp $
.\" $OpenBSD: ksh.1,v 1.144 2012/07/08 08:13:20 guenther Exp $ .\" $OpenBSD: ksh.1,v 1.144 2012/07/08 08:13:20 guenther Exp $
.\"- .\"-
.\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
@ -74,7 +74,7 @@
.\" with -mandoc, it might implement .Mx itself, but we want to .\" with -mandoc, it might implement .Mx itself, but we want to
.\" use our own definition. And .Dd must come *first*, always. .\" use our own definition. And .Dd must come *first*, always.
.\" .\"
.Dd $Mdocdate: October 21 2012 $ .Dd $Mdocdate: October 22 2012 $
.\" .\"
.\" Check which macro package we use, and do other -mdoc setup. .\" Check which macro package we use, and do other -mdoc setup.
.\" .\"
@ -1146,9 +1146,14 @@ command substitutions take the form
.Pf $( Ns Ar command Ns \&) .Pf $( Ns Ar command Ns \&)
or (deprecated) or (deprecated)
.Pf \` Ns Ar command Ns \` .Pf \` Ns Ar command Ns \`
or (executed in the current environment)
.Pf ${\ \& Ar command Ns \&;}
and strip trailing newlines; and strip trailing newlines;
and arithmetic substitutions take the form and arithmetic substitutions take the form
.Pf $(( Ns Ar expression Ns )) . .Pf $(( Ns Ar expression Ns )) .
Parsing the current-environment command substitution requires a space,
tab or newline after the opening brace and that the closing brace be
recognised as a keyword (i.e. is preceded by a newline or semicolon).
.Pp .Pp
If a substitution appears outside of double quotes, the results of the If a substitution appears outside of double quotes, the results of the
substitution are generally subject to word or field splitting according to substitution are generally subject to word or field splitting according to
@ -1226,6 +1231,8 @@ A command substitution is replaced by the output generated by the specified
command which is run in a subshell. command which is run in a subshell.
For For
.Pf $( Ns Ar command Ns \&) .Pf $( Ns Ar command Ns \&)
and
.Pf ${\ \& Ar command Ns \&;}
substitutions, normal quoting rules are used when substitutions, normal quoting rules are used when
.Ar command .Ar command
is parsed; however, for the deprecated is parsed; however, for the deprecated

16
sh.h
View File

@ -157,9 +157,9 @@
#endif #endif
#ifdef EXTERN #ifdef EXTERN
__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.596 2012/10/22 16:53:22 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/sh.h,v 1.597 2012/10/22 20:19:16 tg Exp $");
#endif #endif
#define MKSH_VERSION "R40 2012/10/21" #define MKSH_VERSION "R40 2012/10/22"
/* arithmetic types: C implementation */ /* arithmetic types: C implementation */
#if !HAVE_CAN_INTTYPES #if !HAVE_CAN_INTTYPES
@ -773,8 +773,12 @@ EXTERN const char TC_LEX1[] E_INIT("|&;<>() \t\n");
typedef uint8_t Temp_type; typedef uint8_t Temp_type;
/* expanded heredoc */ /* expanded heredoc */
#define TT_HEREDOC_EXP 0 #define TT_HEREDOC_EXP 0
/* temp file used for history editing (fc -e) */ /* temporary file used for history editing (fc -e) */
#define TT_HIST_EDIT 1 #define TT_HIST_EDIT 1
#ifndef MKSH_DISABLE_EXPERIMENTAL
/* temporary file used during in-situ command substitution */
#define TT_FUNSUB 2
#endif
/* temp/heredoc files. The file is removed when the struct is freed. */ /* temp/heredoc files. The file is removed when the struct is freed. */
struct temp { struct temp {
@ -1286,6 +1290,9 @@ struct op {
#define SPAT 10 /* separate pattern: | */ #define SPAT 10 /* separate pattern: | */
#define CPAT 11 /* close pattern: ) */ #define CPAT 11 /* close pattern: ) */
#define ADELIM 12 /* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */ #define ADELIM 12 /* arbitrary delimiter: ${foo:2:3} ${foo/bar/baz} */
#ifndef MKSH_DISABLE_EXPERIMENTAL
#define FUNSUB 14 /* ${ foo;} substitution (NUL terminated) */
#endif
/* /*
* IO redirection * IO redirection
@ -1321,7 +1328,6 @@ struct ioword {
#define XBGND BIT(2) /* command & */ #define XBGND BIT(2) /* command & */
#define XPIPEI BIT(3) /* input is pipe */ #define XPIPEI BIT(3) /* input is pipe */
#define XPIPEO BIT(4) /* output is pipe */ #define XPIPEO BIT(4) /* output is pipe */
#define XPIPE (XPIPEI|XPIPEO) /* member of pipe */
#define XXCOM BIT(5) /* `...` command */ #define XXCOM BIT(5) /* `...` command */
#define XPCLOSE BIT(6) /* exchild: close close_fd in parent */ #define XPCLOSE BIT(6) /* exchild: close close_fd in parent */
#define XCCLOSE BIT(7) /* exchild: close close_fd in child */ #define XCCLOSE BIT(7) /* exchild: close close_fd in child */
@ -1922,7 +1928,7 @@ ssize_t shf_vfprintf(struct shf *, const char *, va_list)
void initkeywords(void); void initkeywords(void);
struct op *compile(Source *, bool); struct op *compile(Source *, bool);
bool parse_usec(const char *, struct timeval *); bool parse_usec(const char *, struct timeval *);
char *yyrecursive(void); char *yyrecursive(int);
/* tree.c */ /* tree.c */
void fptreef(struct shf *, int, const char *, ...); void fptreef(struct shf *, int, const char *, ...);
char *snptreef(char *, ssize_t, const char *, ...); char *snptreef(char *, ssize_t, const char *, ...);

37
syn.c
View File

@ -23,12 +23,11 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/syn.c,v 1.81 2012/10/03 15:50:32 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/syn.c,v 1.82 2012/10/22 20:19:18 tg Exp $");
extern int subshell_nesting_type;
extern void yyskiputf8bom(void); extern void yyskiputf8bom(void);
extern uint8_t subshell_nesting_level;
struct nesting_state { struct nesting_state {
int start_token; /* token than began nesting (eg, FOR) */ int start_token; /* token than began nesting (eg, FOR) */
int start_line; /* line nesting began on */ int start_line; /* line nesting began on */
@ -364,12 +363,15 @@ get_command(int cf)
Leave: Leave:
break; break;
case '(': /*)*/ case '(': /*)*/ {
int subshell_nesting_type_saved;
Subshell: Subshell:
++subshell_nesting_level; subshell_nesting_type_saved = subshell_nesting_type;
subshell_nesting_type = ')';
t = nested(TPAREN, '(', ')'); t = nested(TPAREN, '(', ')');
--subshell_nesting_level; subshell_nesting_type = subshell_nesting_type_saved;
break; break;
}
case '{': /*}*/ case '{': /*}*/
t = nested(TBRACE, '{', '}'); t = nested(TBRACE, '{', '}');
@ -1116,16 +1118,29 @@ parse_usec(const char *s, struct timeval *tv)
* a COMSUB recursively using the main shell parser and lexer * a COMSUB recursively using the main shell parser and lexer
*/ */
char * char *
yyrecursive(void) yyrecursive(int subtype MKSH_A_UNUSED)
{ {
struct op *t; struct op *t;
char *cp; char *cp;
bool old_reject; bool old_reject;
int old_symbol, old_salias; int old_symbol, old_salias, old_nesting_type;
struct ioword **old_herep; struct ioword **old_herep;
int stok, etok;
#ifndef MKSH_DISABLE_EXPERIMENTAL
if (subtype == FUNSUB) {
stok = '{';
etok = '}';
} else
#endif
{
stok = '(';
etok = ')';
}
/* tell the lexer to accept a closing parenthesis as EOD */ /* tell the lexer to accept a closing parenthesis as EOD */
++subshell_nesting_level; old_nesting_type = subshell_nesting_type;
subshell_nesting_type = etok;
/* push reject state, parse recursively, pop reject state */ /* push reject state, parse recursively, pop reject state */
old_reject = reject; old_reject = reject;
@ -1135,7 +1150,7 @@ yyrecursive(void)
old_salias = sALIAS; old_salias = sALIAS;
sALIAS = 0; sALIAS = 0;
/* we use TPAREN as a helper container here */ /* we use TPAREN as a helper container here */
t = nested(TPAREN, '(', ')'); t = nested(TPAREN, stok, etok);
sALIAS = old_salias; sALIAS = old_salias;
herep = old_herep; herep = old_herep;
reject = old_reject; reject = old_reject;
@ -1145,6 +1160,6 @@ yyrecursive(void)
cp = snptreef(NULL, 0, "%T", t->left); cp = snptreef(NULL, 0, "%T", t->left);
tfree(t, ATEMP); tfree(t, ATEMP);
--subshell_nesting_level; subshell_nesting_type = old_nesting_type;
return (cp); return (cp);
} }

16
tree.c
View File

@ -23,7 +23,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/tree.c,v 1.64 2012/08/17 18:34:25 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/tree.c,v 1.65 2012/10/22 20:19:18 tg Exp $");
#define INDENT 8 #define INDENT 8
@ -342,6 +342,12 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
shf_putc(c, shf); shf_putc(c, shf);
shf_puts(cs, shf); shf_puts(cs, shf);
break; break;
#ifndef MKSH_DISABLE_EXPERIMENTAL
case FUNSUB:
shf_puts("${ ", shf);
cs = ";}";
goto pSUB;
#endif
case EXPRSUB: case EXPRSUB:
shf_puts("$((", shf); shf_puts("$((", shf);
cs = "))"; cs = "))";
@ -581,6 +587,9 @@ wdscan(const char *wp, int c)
wp++; wp++;
break; break;
case COMSUB: case COMSUB:
#ifndef MKSH_DISABLE_EXPERIMENTAL
case FUNSUB:
#endif
case EXPRSUB: case EXPRSUB:
while (*wp++ != 0) while (*wp++ != 0)
; ;
@ -820,6 +829,11 @@ dumpwdvar_i(struct shf *shf, const char *wp, int quotelevel)
closeandout: closeandout:
shf_putc('>', shf); shf_putc('>', shf);
break; break;
#ifndef MKSH_DISABLE_EXPERIMENTAL
case FUNSUB:
shf_puts("FUNSUB<", shf);
goto dumpsub;
#endif
case EXPRSUB: case EXPRSUB:
shf_puts("EXPRSUB<", shf); shf_puts("EXPRSUB<", shf);
goto dumpsub; goto dumpsub;