hush: support "f() (cmd)" functions

Many other shells support this construct

function                                             old     new   delta
parse_stream                                        2950    3018     +68

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/shell/ash_test/ash-misc/func5.right b/shell/ash_test/ash-misc/func5.right
index 2c9d316..01e79c3 100644
--- a/shell/ash_test/ash-misc/func5.right
+++ b/shell/ash_test/ash-misc/func5.right
@@ -1,6 +1,3 @@
 1
 2
 3
-1
-2
-3
diff --git a/shell/ash_test/ash-misc/func5.tests b/shell/ash_test/ash-misc/func5.tests
index e967208..5c33560 100755
--- a/shell/ash_test/ash-misc/func5.tests
+++ b/shell/ash_test/ash-misc/func5.tests
@@ -6,8 +6,3 @@
 
 f() ( echo $1 )
 f 3
-
-f() for i in 1 2 3; do
-	echo $i
-done
-f
diff --git a/shell/ash_test/ash-misc/func_compound1.right b/shell/ash_test/ash-misc/func_compound1.right
new file mode 100644
index 0000000..01e79c3
--- /dev/null
+++ b/shell/ash_test/ash-misc/func_compound1.right
@@ -0,0 +1,3 @@
+1
+2
+3
diff --git a/shell/ash_test/ash-misc/func_compound1.tests b/shell/ash_test/ash-misc/func_compound1.tests
new file mode 100755
index 0000000..20c8bf1
--- /dev/null
+++ b/shell/ash_test/ash-misc/func_compound1.tests
@@ -0,0 +1,4 @@
+f() for i in 1 2 3; do
+	echo $i
+done
+f
diff --git a/shell/hush.c b/shell/hush.c
index 94e429c..3351072 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -4297,6 +4297,11 @@
 	/* dest contains characters seen prior to ( or {.
 	 * Typically it's empty, but for function defs,
 	 * it contains function name (without '()'). */
+#if BB_MMU
+# define as_string NULL
+#else
+	char *as_string = NULL;
+#endif
 	struct pipe *pipe_list;
 	int endch;
 	struct command *command = ctx->command;
@@ -4325,7 +4330,7 @@
 		do
 			ch = i_getch(input);
 		while (ch == ' ' || ch == '\t' || ch == '\n');
-		if (ch != '{') {
+		if (ch != '{' && ch != '(') {
 			syntax_error_unexpected_ch(ch);
 			return 1;
 		}
@@ -4347,13 +4352,13 @@
 	}
 #endif
 
-#if ENABLE_HUSH_FUNCTIONS
- skip:
-#endif
+ IF_HUSH_FUNCTIONS(skip:)
+
 	endch = '}';
 	if (ch == '(') {
 		endch = ')';
-		command->cmd_type = CMD_SUBSHELL;
+		IF_HUSH_FUNCTIONS(if (command->cmd_type != CMD_FUNCDEF))
+			command->cmd_type = CMD_SUBSHELL;
 	} else {
 		/* bash does not allow "{echo...", requires whitespace */
 		ch = i_peek(input);
@@ -4369,38 +4374,54 @@
 		}
 	}
 
-	{
-#if BB_MMU
-# define as_string NULL
-#else
-		char *as_string = NULL;
-#endif
-		pipe_list = parse_stream(&as_string, input, endch);
+	pipe_list = parse_stream(&as_string, input, endch);
 #if !BB_MMU
-		if (as_string)
-			o_addstr(&ctx->as_string, as_string);
+	if (as_string)
+		o_addstr(&ctx->as_string, as_string);
 #endif
-		/* empty ()/{} or parse error? */
-		if (!pipe_list || pipe_list == ERR_PTR) {
-			/* parse_stream already emitted error msg */
-			if (!BB_MMU)
-				free(as_string);
-			debug_printf_parse("parse_group return 1: "
-				"parse_stream returned %p\n", pipe_list);
-			return 1;
-		}
-		command->group = pipe_list;
-#if !BB_MMU
-		as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */
-		command->group_as_string = as_string;
-		debug_printf_parse("end of group, remembering as:'%s'\n",
-				command->group_as_string);
-#endif
-#undef as_string
+
+	/* empty ()/{} or parse error? */
+	if (!pipe_list || pipe_list == ERR_PTR) {
+		/* parse_stream already emitted error msg */
+		if (!BB_MMU)
+			free(as_string);
+		debug_printf_parse("parse_group return 1: "
+			"parse_stream returned %p\n", pipe_list);
+		return 1;
 	}
+#if !BB_MMU
+	as_string[strlen(as_string) - 1] = '\0'; /* plink ')' or '}' */
+	command->group_as_string = as_string;
+	debug_printf_parse("end of group, remembering as:'%s'\n",
+			command->group_as_string);
+#endif
+
+#if ENABLE_HUSH_FUNCTIONS
+	/* Convert "f() (cmds)" to "f() {(cmds)}" */
+	if (command->cmd_type == CMD_FUNCDEF && endch == ')') {
+		struct command *cmd2;
+
+		cmd2 = xzalloc(sizeof(*cmd2));
+		cmd2->cmd_type = CMD_SUBSHELL;
+		cmd2->group = pipe_list;
+# if !BB_MMU
+//UNTESTED!
+		cmd2->group_as_string = command->group_as_string;
+		command->group_as_string = xasprintf("(%s)", command->group_as_string);
+# endif
+
+		pipe_list = new_pipe();
+		pipe_list->cmds = cmd2;
+		pipe_list->num_cmds = 1;
+	}
+#endif
+
+	command->group = pipe_list;
+
 	debug_printf_parse("parse_group return 0\n");
 	return 0;
 	/* command remains "open", available for possible redirects */
+#undef as_string
 }
 
 #if ENABLE_HUSH_TICK || ENABLE_FEATURE_SH_MATH || ENABLE_HUSH_DOLLAR_OPS
diff --git a/shell/hush_test/hush-misc/func5.tests b/shell/hush_test/hush-misc/func5.tests
index 9c5f9fa..5c33560 100755
--- a/shell/hush_test/hush-misc/func5.tests
+++ b/shell/hush_test/hush-misc/func5.tests
@@ -1,9 +1,8 @@
 f() { echo $1; }
 f 1
 
-# hush fails on this syntax, but i've never seen anyone use it ...
-#f() ( echo $1; )
+f() ( echo $1; )
 f 2
 
-#f() ( echo $1 )
+f() ( echo $1 )
 f 3