From bfe7d78d40560070adae184efcaaca220da75f83 Mon Sep 17 00:00:00 2001
From: tg <tg@mirbsd.org>
Date: Mon, 22 Oct 2012 20:19:18 +0000
Subject: [PATCH] bring back ${ foo;} sans dot.mkshrc patch, using a temporary
 file, and as experimental feature

---
 Build.sh |  7 +++++--
 check.t  | 27 ++++++++++++++++++++++++---
 eval.c   | 51 +++++++++++++++++++++++++++++++++++++++++++++------
 lex.c    | 33 ++++++++++++++++++++++++++++-----
 main.c   | 16 ++++++++++++++--
 mksh.1   | 11 +++++++++--
 sh.h     | 16 +++++++++++-----
 syn.c    | 37 ++++++++++++++++++++++++++-----------
 tree.c   | 16 +++++++++++++++-
 9 files changed, 177 insertions(+), 37 deletions(-)

diff --git a/Build.sh b/Build.sh
index 7b4c405..585ca2a 100644
--- a/Build.sh
+++ b/Build.sh
@@ -1,5 +1,5 @@
 #!/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,
 #		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 '' \
 #    "if deprecated features are to be omitted" && \
 #    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
@@ -1492,7 +1495,7 @@ else
 		#define EXTERN
 		#define MKSH_INCLUDES_ONLY
 		#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); }
 EOF
 	case $cm in
diff --git a/check.t b/check.t
index b4951e2..ccb38ca 100644
--- a/check.t
+++ b/check.t
@@ -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: 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 $
@@ -29,7 +29,7 @@
 # http://www.freebsd.org/cgi/cvsweb.cgi/src/tools/regression/bin/test/regress.sh?rev=HEAD
 
 expected-stdout:
-	@(#)MIRBSD KSH R40 2012/10/21
+	@(#)MIRBSD KSH R40 2012/10/22
 description:
 	Check version of shell.
 stdin:
@@ -38,7 +38,7 @@ name: KSH_VERSION
 category: shell:legacy-no
 ---
 expected-stdout:
-	@(#)LEGACY KSH R40 2012/10/21
+	@(#)LEGACY KSH R40 2012/10/22
 description:
 	Check version of legacy shell.
 stdin:
@@ -9553,6 +9553,27 @@ expected-stdout:
 		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
 description:
 	Check that the short form [ $x ] works
diff --git a/eval.c b/eval.c
index 119d2bd..f0a5c00 100644
--- a/eval.c
+++ b/eval.c
@@ -23,7 +23,7 @@
 
 #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
@@ -58,7 +58,7 @@ typedef struct Expand {
 #define IFS_NWS		2	/* have seen IFS non-white-space */
 
 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 void glob(char *, XPtrV *, bool);
 static void globit(XString *, char **, char *, XPtrV *, int);
@@ -277,18 +277,33 @@ expand(const char *cp,	/* input word */
 				quote = st->quotew;
 				continue;
 			case COMSUB:
+#ifndef MKSH_DISABLE_EXPERIMENTAL
+			case FUNSUB:
+#endif
 				tilde_ok = 0;
 				if (f & DONTRUNCOMMAND) {
 					word = IFS_WORD;
 					*dp++ = '$';
-					*dp++ = '(';
+#ifndef MKSH_DISABLE_EXPERIMENTAL
+					if (c == FUNSUB) {
+						*dp++ = '{';
+						*dp++ = ' ';
+					} else
+#endif
+						*dp++ = '(';
 					while (*sp != '\0') {
 						Xcheck(ds, dp);
 						*dp++ = *sp++;
 					}
-					*dp++ = ')';
+#ifndef MKSH_DISABLE_EXPERIMENTAL
+					if (c == FUNSUB) {
+						*dp++ = ';';
+						*dp++ = '}';
+					} else
+#endif
+						*dp++ = ')';
 				} else {
-					type = comsub(&x, sp);
+					type = comsub(&x, sp, c);
 					if (type == XCOM && (f&DOBLANK))
 						doblank++;
 					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.
  */
 static int
-comsub(Expand *xp, const char *cp)
+comsub(Expand *xp, const char *cp, int fn MKSH_A_UNUSED)
 {
 	Source *s, *sold;
 	struct op *t;
@@ -1307,6 +1322,30 @@ comsub(Expand *xp, const char *cp)
 			SHF_MAPHI|SHF_CLEXEC);
 		if (shf == NULL)
 			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 {
 		int ofd1, pv[2];
 
diff --git a/lex.c b/lex.c
index 22e956e..2a81495 100644
--- a/lex.c
+++ b/lex.c
@@ -23,7 +23,7 @@
 
 #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
@@ -109,7 +109,7 @@ void yyskiputf8bom(void);
 static int backslash_skip;
 static int ignore_backslash_newline;
 static struct sretrace_info *retrace_info;
-uint8_t subshell_nesting_level = 0;
+int subshell_nesting_type = 0;
 
 /* optimised getsc_bn() */
 #define o_getsc()	(*source->str != '\0' && *source->str != '\\' && \
@@ -262,6 +262,13 @@ yylex(int cf)
 	while (!((c = getsc()) == 0 ||
 	    ((state == SBASE || state == SHEREDELIM || state == SHERESTRING) &&
 	    ctype(c, C_LEX1)))) {
+#ifndef MKSH_DISABLE_EXPERIMENTAL
+		if (state == SBASE &&
+		    subshell_nesting_type == /*{*/ '}' &&
+		    c == /*{*/ '}')
+			/* possibly end ${ :;} */
+			break;
+#endif
  accept_nonword:
 		Xcheck(ws, wp);
 		switch (state) {
@@ -395,14 +402,30 @@ yylex(int cf)
 					} else {
 						ungetsc(c);
  subst_command:
-						sp = yyrecursive();
+						c = COMSUB;
+#ifndef MKSH_DISABLE_EXPERIMENTAL
+ subst_command2:
+#endif
+						sp = yyrecursive(c);
 						cz = strlen(sp) + 1;
 						XcheckN(ws, wp, cz);
-						*wp++ = COMSUB;
+						*wp++ = c;
 						memcpy(wp, sp, cz);
 						wp += cz;
 					}
 				} 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++ = '{'; /*}*/
 					wp = get_brace_var(&ws, wp);
@@ -1171,7 +1194,7 @@ readhere(struct ioword *iop)
 		/* end of here document marker, what to do? */
 		switch (c) {
 		case /*(*/ ')':
-			if (!subshell_nesting_level)
+			if (!subshell_nesting_type)
 				/*-
 				 * not allowed outside $(...) or (...)
 				 * => mismatch
diff --git a/main.c b/main.c
index 7e448aa..15a132a 100644
--- a/main.c
+++ b/main.c
@@ -34,7 +34,7 @@
 #include <locale.h>
 #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;
 
@@ -1377,7 +1377,7 @@ void
 restfd(int fd, int ofd)
 {
 	if (fd == 2)
-		shf_flush(&shf_iob[fd]);
+		shf_flush(&shf_iob[/* fd */ 2]);
 	if (ofd < 0)
 		/* original fd closed */
 		close(fd);
@@ -1591,6 +1591,18 @@ maketemp(Area *ap, Temp_type type, struct temp **tlist)
 		/* 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 */
 	tp->shf = shf_fdopen(i, SHF_WR, NULL);
 
diff --git a/mksh.1 b/mksh.1
index 5f1b2a6..2537e52 100644
--- a/mksh.1
+++ b/mksh.1
@@ -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 $
 .\"-
 .\" Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009,
@@ -74,7 +74,7 @@
 .\" with -mandoc, it might implement .Mx itself, but we want to
 .\" 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.
 .\"
@@ -1146,9 +1146,14 @@ command substitutions take the form
 .Pf $( Ns Ar command Ns \&)
 or (deprecated)
 .Pf \` Ns Ar command Ns \`
+or (executed in the current environment)
+.Pf ${\ \& Ar command Ns \&;}
 and strip trailing newlines;
 and arithmetic substitutions take the form
 .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
 If a substitution appears outside of double quotes, the results of the
 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.
 For
 .Pf $( Ns Ar command Ns \&)
+and
+.Pf ${\ \& Ar command Ns \&;}
 substitutions, normal quoting rules are used when
 .Ar command
 is parsed; however, for the deprecated
diff --git a/sh.h b/sh.h
index 344db1a..bcd9831 100644
--- a/sh.h
+++ b/sh.h
@@ -157,9 +157,9 @@
 #endif
 
 #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
-#define MKSH_VERSION "R40 2012/10/21"
+#define MKSH_VERSION "R40 2012/10/22"
 
 /* arithmetic types: C implementation */
 #if !HAVE_CAN_INTTYPES
@@ -773,8 +773,12 @@ EXTERN const char TC_LEX1[] E_INIT("|&;<>() \t\n");
 typedef uint8_t Temp_type;
 /* expanded heredoc */
 #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
+#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. */
 struct temp {
@@ -1286,6 +1290,9 @@ struct op {
 #define SPAT	10	/* separate pattern: | */
 #define CPAT	11	/* close pattern: ) */
 #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
@@ -1321,7 +1328,6 @@ struct ioword {
 #define XBGND	BIT(2)		/* command & */
 #define XPIPEI	BIT(3)		/* input is pipe */
 #define XPIPEO	BIT(4)		/* output is pipe */
-#define XPIPE	(XPIPEI|XPIPEO)	/* member of pipe */
 #define XXCOM	BIT(5)		/* `...` command */
 #define XPCLOSE	BIT(6)		/* exchild: close close_fd in parent */
 #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);
 struct op *compile(Source *, bool);
 bool parse_usec(const char *, struct timeval *);
-char *yyrecursive(void);
+char *yyrecursive(int);
 /* tree.c */
 void fptreef(struct shf *, int, const char *, ...);
 char *snptreef(char *, ssize_t, const char *, ...);
diff --git a/syn.c b/syn.c
index 44a4337..43d17c5 100644
--- a/syn.c
+++ b/syn.c
@@ -23,12 +23,11 @@
 
 #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 uint8_t subshell_nesting_level;
-
 struct nesting_state {
 	int start_token;	/* token than began nesting (eg, FOR) */
 	int start_line;		/* line nesting began on */
@@ -364,12 +363,15 @@ get_command(int cf)
  Leave:
 		break;
 
-	case '(': /*)*/
+	case '(': /*)*/ {
+		int subshell_nesting_type_saved;
  Subshell:
-		++subshell_nesting_level;
+		subshell_nesting_type_saved = subshell_nesting_type;
+		subshell_nesting_type = ')';
 		t = nested(TPAREN, '(', ')');
-		--subshell_nesting_level;
+		subshell_nesting_type = subshell_nesting_type_saved;
 		break;
+	    }
 
 	case '{': /*}*/
 		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
  */
 char *
-yyrecursive(void)
+yyrecursive(int subtype MKSH_A_UNUSED)
 {
 	struct op *t;
 	char *cp;
 	bool old_reject;
-	int old_symbol, old_salias;
+	int old_symbol, old_salias, old_nesting_type;
 	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 */
-	++subshell_nesting_level;
+	old_nesting_type = subshell_nesting_type;
+	subshell_nesting_type = etok;
 
 	/* push reject state, parse recursively, pop reject state */
 	old_reject = reject;
@@ -1135,7 +1150,7 @@ yyrecursive(void)
 	old_salias = sALIAS;
 	sALIAS = 0;
 	/* we use TPAREN as a helper container here */
-	t = nested(TPAREN, '(', ')');
+	t = nested(TPAREN, stok, etok);
 	sALIAS = old_salias;
 	herep = old_herep;
 	reject = old_reject;
@@ -1145,6 +1160,6 @@ yyrecursive(void)
 	cp = snptreef(NULL, 0, "%T", t->left);
 	tfree(t, ATEMP);
 
-	--subshell_nesting_level;
+	subshell_nesting_type = old_nesting_type;
 	return (cp);
 }
diff --git a/tree.c b/tree.c
index 678f530..1669473 100644
--- a/tree.c
+++ b/tree.c
@@ -23,7 +23,7 @@
 
 #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
 
@@ -342,6 +342,12 @@ wdvarput(struct shf *shf, const char *wp, int quotelevel, int opmode)
 				shf_putc(c, shf);
 			shf_puts(cs, shf);
 			break;
+#ifndef MKSH_DISABLE_EXPERIMENTAL
+		case FUNSUB:
+			shf_puts("${ ", shf);
+			cs = ";}";
+			goto pSUB;
+#endif
 		case EXPRSUB:
 			shf_puts("$((", shf);
 			cs = "))";
@@ -581,6 +587,9 @@ wdscan(const char *wp, int c)
 			wp++;
 			break;
 		case COMSUB:
+#ifndef MKSH_DISABLE_EXPERIMENTAL
+		case FUNSUB:
+#endif
 		case EXPRSUB:
 			while (*wp++ != 0)
 				;
@@ -820,6 +829,11 @@ dumpwdvar_i(struct shf *shf, const char *wp, int quotelevel)
  closeandout:
 			shf_putc('>', shf);
 			break;
+#ifndef MKSH_DISABLE_EXPERIMENTAL
+		case FUNSUB:
+			shf_puts("FUNSUB<", shf);
+			goto dumpsub;
+#endif
 		case EXPRSUB:
 			shf_puts("EXPRSUB<", shf);
 			goto dumpsub;