hush: first stab at function support. argv passing is not coded yet.
 Only very rudimentary testing was done.
 With function support off, code growth is zero, with it on:

function                                             old     new   delta
run_list                                            2158    2339    +181
parse_stream                                        1929    2044    +115
find_builtin                                          24      67     +43
find_function                                          -      36     +36
file_get                                             244     264     +20
pseudo_exec_argv                                     145     160     +15
free_strings                                           -       7      +7
free_pipe                                            183     181      -2
done_word                                            735     728      -7
expand_variables                                    2227    2204     -23
------------------------------------------------------------------------------
(add/remove: 2/0 grow/shrink: 5/3 up/down: 417/-32)           Total: 385 bytes

diff --git a/shell/hush.c b/shell/hush.c
index 9adf0e1..3728583 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -86,7 +86,7 @@
  */
 #define HUSH_DEBUG 1
 /* In progress... */
-#define ENABLE_HUSH_FUNCTIONS 0
+#define ENABLE_HUSH_FUNCTIONS 1
 
 
 #if BUILD_AS_NOMMU
@@ -374,12 +374,12 @@
 #define GRP_NORMAL   0
 #define GRP_SUBSHELL 1
 #if ENABLE_HUSH_FUNCTIONS
-#define GRP_FUNCTION 2
+# define GRP_FUNCTION 2
 #endif
 
 struct pipe {
 	struct pipe *next;
-	int num_cmds;               /* total number of commands in job */
+	int num_cmds;               /* total number of commands in pipe */
 	int alive_cmds;             /* number of commands running (not exited) */
 	int stopped_cmds;           /* number of commands alive, but stopped */
 #if ENABLE_HUSH_JOB
@@ -452,6 +452,12 @@
 	BC_CONTINUE = 2,
 };
 
+struct function {
+	struct function *next;
+	char *name;
+	struct pipe *body;
+};
+
 
 /* "Globals" within this file */
 /* Sorted roughly by size (smaller offsets == smaller code) */
@@ -504,6 +510,7 @@
 	const char *cwd;
 	struct variable *top_var; /* = &G.shell_ver (set in main()) */
 	struct variable shell_ver;
+	struct function *top_func;
 	/* Signal and trap handling */
 //	unsigned count_SIGCHLD;
 //	unsigned handled_SIGCHLD;
@@ -2585,6 +2592,35 @@
 }
 
 
+static const struct built_in_command* find_builtin(const char *name)
+{
+	const struct built_in_command *x;
+	for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
+		if (strcmp(name, x->cmd) != 0)
+			continue;
+		debug_printf_exec("found builtin '%s'\n", name);
+		return x;
+	}
+	return NULL;
+}
+
+# if ENABLE_HUSH_FUNCTIONS
+static const struct function *find_function(const char *name)
+{
+	const struct function *funcp = G.top_func;
+	while (funcp) {
+		if (strcmp(name, funcp->name) != 0)
+			continue;
+		return funcp;
+		debug_printf_exec("found function '%s'\n", name);
+	}
+	return NULL;
+}
+#endif
+
+
+static int run_list(struct pipe *pi);
+
 #if BB_MMU
 #define pseudo_exec_argv(nommu_save, argv, assignment_cnt, argv_expanded) \
 	pseudo_exec_argv(argv, assignment_cnt, argv_expanded)
@@ -2627,6 +2663,11 @@
 #endif
 	}
 
+#if ENABLE_FEATURE_SH_STANDALONE || BB_MMU
+	if (strchr(argv[0], '/') != NULL)
+		goto skip;
+#endif
+
 	/* On NOMMU, we must never block!
 	 * Example: { sleep 99999 | read line } & echo Ok
 	 * read builtin will block on read syscall, leaving parent blocked
@@ -2640,30 +2681,38 @@
 	 */
 	{
 		int rcode;
-		const struct built_in_command *x;
-		for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
-			if (strcmp(argv[0], x->cmd) == 0) {
-				debug_printf_exec("running builtin '%s'\n",
-						argv[0]);
-				rcode = x->function(argv);
-				fflush(NULL);
-				_exit(rcode);
-			}
+		const struct built_in_command *x = find_builtin(argv[0]);
+		if (x) {
+			rcode = x->function(argv);
+			fflush(NULL);
+			_exit(rcode);
 		}
 	}
+# if ENABLE_HUSH_FUNCTIONS
+	/* Check if the command matches any functions */
+	{
+		int rcode;
+		const struct function *funcp = find_function(argv[0]);
+		if (funcp) {
+			rcode = run_list(funcp->body);
+			fflush(NULL);
+			_exit(rcode);
+		}
+	}
+# endif
 #endif
 
 #if ENABLE_FEATURE_SH_STANDALONE
 	/* Check if the command matches any busybox applets */
-	if (strchr(argv[0], '/') == NULL) {
+	{
 		int a = find_applet_by_name(argv[0]);
 		if (a >= 0) {
-#if BB_MMU /* see above why on NOMMU it is not allowed */
+# if BB_MMU /* see above why on NOMMU it is not allowed */
 			if (APPLET_IS_NOEXEC(a)) {
 				debug_printf_exec("running applet '%s'\n", argv[0]);
 				run_applet_no_and_exit(a, argv);
 			}
-#endif
+# endif
 			/* Re-exec ourselves */
 			debug_printf_exec("re-execing applet '%s'\n", argv[0]);
 			sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
@@ -2674,6 +2723,7 @@
 	}
 #endif
 
+ skip:
 	debug_printf_exec("execing '%s'\n", argv[0]);
 	sigprocmask(SIG_SETMASK, &G.inherited_set, NULL);
 	execvp(argv[0], argv);
@@ -2681,8 +2731,6 @@
 	_exit(EXIT_FAILURE);
 }
 
-static int run_list(struct pipe *pi);
-
 /* Called after [v]fork() in run_pipe
  */
 static void pseudo_exec(nommu_save_t *nommu_save,
@@ -3031,8 +3079,35 @@
 	if (command->group) {
 #if ENABLE_HUSH_FUNCTIONS
 		if (command->grp_type == GRP_FUNCTION) {
-			/* func () { list } */
-			bb_error_msg("here we ought to remember function definition, and go on");
+			/* "executing" func () { list } */
+			struct function *funcp;
+			struct function **funcpp = &G.top_func;
+
+			while ((funcp = *funcpp) != NULL) {
+				if (strcmp(funcp->name, command->argv[0]) == 0) {
+					debug_printf_exec("replacing function '%s'", funcp->name);
+					free(funcp->name);
+					free_pipe_list(funcp->body, /* indent: */ 0);
+					goto skip;
+				}
+				funcpp = &funcp->next;
+			}
+			debug_printf_exec("remembering new function '%s'", funcp->name);
+			funcp = *funcpp = xzalloc(sizeof(*funcp));
+			/*funcp->next = NULL;*/
+ skip:
+			funcp->name = command->argv[0];
+			funcp->body = command->group;
+			command->group = NULL;
+			command->argv[0] = NULL;
+			free_strings(command->argv);
+			command->argv = NULL;
+			/* note: if we are in a loop, future "executions"
+			 * of func def will see it as null command since
+			 * command->group == NULL and command->argv == NULL */
+//this isn't exactly right: while...do f1() {a;}; f1; f1 {b;}; done
+//second loop will execute b!
+
 			return EXIT_SUCCESS;
 		}
 #endif
@@ -3052,6 +3127,11 @@
 	argv = command->argv ? command->argv : (char **) &null_ptr;
 	{
 		const struct built_in_command *x;
+#if ENABLE_HUSH_FUNCTIONS
+		const struct function *funcp;
+#else
+		enum { funcp = 0 };
+#endif
 		char **new_env = NULL;
 		char **old_env = NULL;
 
@@ -3078,13 +3158,18 @@
 		/* Expand the rest into (possibly) many strings each */
 		argv_expanded = expand_strvec_to_strvec(argv + command->assignment_cnt);
 
-		for (x = bltins; x != &bltins[ARRAY_SIZE(bltins)]; x++) {
-			if (strcmp(argv_expanded[0], x->cmd) != 0)
-				continue;
-			if (x->function == builtin_exec && argv_expanded[1] == NULL) {
-				debug_printf("exec with redirects only\n");
-				rcode = setup_redirects(command, NULL);
-				goto clean_up_and_ret1;
+		x = find_builtin(argv_expanded[0]);
+#if ENABLE_HUSH_FUNCTIONS
+		if (!x)
+			funcp = find_function(argv_expanded[0]);
+#endif
+		if (x || funcp) {
+			if (!funcp) {
+				if (x->function == builtin_exec && argv_expanded[1] == NULL) {
+					debug_printf("exec with redirects only\n");
+					rcode = setup_redirects(command, NULL);
+					goto clean_up_and_ret1;
+				}
 			}
 			debug_printf("builtin inline %s\n", argv_expanded[0]);
 			/* XXX setup_redirects acts on file descriptors, not FILEs.
@@ -3095,9 +3180,18 @@
 			if (rcode == 0) {
 				new_env = expand_assignments(argv, command->assignment_cnt);
 				old_env = putenv_all_and_save_old(new_env);
-				debug_printf_exec(": builtin '%s' '%s'...\n",
+				if (!funcp) {
+					debug_printf_exec(": builtin '%s' '%s'...\n",
 						x->cmd, argv_expanded[1]);
-				rcode = x->function(argv_expanded) & 0xff;
+					rcode = x->function(argv_expanded) & 0xff;
+				}
+#if ENABLE_HUSH_FUNCTIONS
+				else {
+					debug_printf_exec(": function '%s' '%s'...\n",
+						funcp->name, argv_expanded[1]);
+					rcode = run_list(funcp->body) & 0xff;
+				}
+#endif
 			}
 #if ENABLE_FEATURE_SH_STANDALONE
  clean_up_and_ret:
@@ -3114,6 +3208,7 @@
 			debug_printf_exec("run_pipe return %d\n", rcode);
 			return rcode;
 		}
+
 #if ENABLE_FEATURE_SH_STANDALONE
 		i = find_applet_by_name(argv_expanded[0]);
 		if (i >= 0 && APPLET_IS_NOFORK(i)) {
@@ -4081,6 +4176,10 @@
 			}
 		}
 		command->argv = add_string_to_strings(command->argv, xstrdup(word->data));
+//SEGV, but good idea.
+//		command->argv = add_string_to_strings(command->argv, word->data);
+//		word->data = NULL;
+//		word->length = 0;
 		debug_print_strings("word appended to argv", command->argv);
 	}
 
@@ -4089,7 +4188,7 @@
 		if (word->o_quoted
 		 || !is_well_formed_var_name(command->argv[0], '\0')
 		) {
-			/* bash says "not a valid identifier" */
+			/* bash says just "not a valid identifier" */
 			syntax_error("not a valid identifier in for");
 			return 1;
 		}
@@ -4462,27 +4561,55 @@
 
 	debug_printf_parse("parse_group entered\n");
 #if ENABLE_HUSH_FUNCTIONS
-	if (ch == 'F') { /* function definition? */
-		bb_error_msg("aha '%s' is a function, parsing it...", dest->data);
-		//command->fname = dest->data;
-		command->grp_type = GRP_FUNCTION;
-		memset(dest, 0, sizeof(*dest));
+	if (ch == '(') {
+		if (!dest->o_quoted) {
+			if (dest->length)
+				done_word(dest, ctx);
+			if (!command->argv)
+				goto skip; /* (... */
+			if (command->argv[1]) { /* word word ... (... */
+				syntax_error("unexpected character (");
+				return 1;
+			}
+			/* it is "word(..." or "word (..." */
+			do
+				ch = i_getch(input);
+			while (ch == ' ' || ch == '\t');
+			if (ch != ')') {
+				syntax_error("unexpected character X");
+				return 1;
+			}
+			do
+				ch = i_getch(input);
+			while (ch == ' ' || ch == '\t' || ch == '\n');
+			if (ch != '{') {
+				syntax_error("unexpected character X");
+				return 1;
+			}
+			command->grp_type = GRP_FUNCTION;
+			goto skip;
+		}
 	}
 #endif
-	if (command->argv /* word [word](... */
-	 || dest->length /* word(... */
-	 || dest->o_quoted /* ""(... */
+	if (command->argv /* word [word]{... */
+	 || dest->length /* word{... */
+	 || dest->o_quoted /* ""{... */
 	) {
 		syntax_error(NULL);
 		debug_printf_parse("parse_group return 1: "
 			"syntax error, groups and arglists don't mix\n");
 		return 1;
 	}
+
+#if ENABLE_HUSH_FUNCTIONS
+ skip:
+#endif
 	endch = '}';
 	if (ch == '(') {
 		endch = ')';
 		command->grp_type = GRP_SUBSHELL;
 	}
+
 	{
 #if !BB_MMU
 		char *as_string = NULL;
@@ -5311,28 +5438,6 @@
 				continue;
 			}
 #endif
-#if ENABLE_HUSH_FUNCTIONS
-			if (dest.length != 0 /* not just () but word() */
-			 && dest.o_quoted == 0 /* not a"b"c() */
-			 && ctx.command->argv == NULL /* it's the first word */
-//TODO: "func ( ) {...}" - note spaces - is valid format too in bash
-			 && i_peek(input) == ')'
-			 && !match_reserved_word(&dest)
-			) {
-				bb_error_msg("seems like a function definition");
-				i_getch(input);
-//if !BB_MMU o_addchr(&ctx.as_string...
-				do {
-//TODO: do it properly.
-					ch = i_getch(input);
-				} while (ch == ' ' || ch == '\n');
-				if (ch != '{') {
-					syntax_error("was expecting {");
-					goto parse_error;
-				}
-				ch = 'F'; /* magic value */
-			}
-#endif
 		case '{':
 			if (parse_group(&dest, &ctx, input, ch) != 0) {
 				goto parse_error;
@@ -6427,11 +6532,11 @@
 				ret = EXIT_FAILURE;
 			}
 		}
-#if ENABLE_HUSH_FUNCTIONS
-		else {
-			unset_local_func(*argv);
-		}
-#endif
+//#if ENABLE_HUSH_FUNCTIONS
+//		else {
+//			unset_local_func(*argv);
+//		}
+//#endif
 		argv++;
 	}
 	return ret;