implement most POSIX parameter expansions (~+500bytes)
diff --git a/shell/hush.c b/shell/hush.c
index 9aeb0f6..84dd9e1 100644
--- a/shell/hush.c
+++ b/shell/hush.c
@@ -43,12 +43,13 @@
  *      Here Documents ( << word )
  *      Functions
  *      Tilde Expansion
- *      fancy forms of Parameter Expansion: ${var:-val}
+ *      Parameter Expansion for substring processing ${var#word} ${var%word}
  *
  * Bash stuff maybe optional enable:
  *      &> and >& redirection of stdout+stderr
  *      Brace expansion
  *      reserved words: [[ ]] function select
+ *      substrings ${var:1:5}
  *
  * Major bugs:
  *      job handling woefully incomplete and buggy (improved --vda)
@@ -589,18 +590,20 @@
 
 #if 1
 /* Normal */
-static void syntax(const char *msg)
+static void maybe_die(const char *notice, const char *msg)
 {
-#if ENABLE_HUSH_INTERACTIVE
 	/* Was using fancy stuff:
 	 * (G.interactive_fd ? bb_error_msg : bb_error_msg_and_die)(...params...)
 	 * but it SEGVs. ?! Oh well... explicit temp ptr works around that */
-	void FAST_FUNC (*fp)(const char *s, ...);
+	void FAST_FUNC (*fp)(const char *s, ...) = bb_error_msg_and_die;
+#if ENABLE_HUSH_INTERACTIVE
 	fp = (G.interactive_fd ? bb_error_msg : bb_error_msg_and_die);
-	fp(msg ? "%s: %s" : "syntax error", "syntax error", msg);
-#else
-	bb_error_msg_and_die(msg ? "%s: %s" : "syntax error", "syntax error", msg);
 #endif
+	fp(msg ? "%s: %s" : notice, notice, msg);
+}
+static void syntax(const char *msg)
+{
+	maybe_die("syntax error", msg);
 }
 #else
 /* Debug */
@@ -920,7 +923,13 @@
 }
 
 /* str holds "NAME=VAL" and is expected to be malloced.
- * We take ownership of it. */
+ * We take ownership of it.
+ * flg_export is used by:
+ *  0: do not export
+ *  1: export
+ * -1: if NAME is set, leave export status alone
+ *     if NAME is not set, do not export
+ */
 static int set_local_var(char *str, int flg_export)
 {
 	struct variable *cur;
@@ -980,7 +989,7 @@
  set_str_and_exp:
 	cur->varstr = str;
  exp:
-	if (flg_export)
+	if (flg_export == 1)
 		cur->flg_export = 1;
 	if (cur->flg_export) {
 		debug_printf_env("%s: putenv '%s'\n", __func__, cur->varstr);
@@ -1548,6 +1557,9 @@
 			val = utoa(G.last_return_code);
 			break;
 		case '#': /* argc */
+			if (arg[1] != SPECIAL_VAR_SYMBOL)
+				/* actually, it's a ${#var} */
+				goto case_default;
 			val = utoa(G.global_argc ? G.global_argc-1 : 0);
 			break;
 		case '*':
@@ -1615,20 +1627,79 @@
 		}
 #endif
 		default: /* <SPECIAL_VAR_SYMBOL>varname<SPECIAL_VAR_SYMBOL> */
+		case_default: {
+			bool exp_len = false, exp_null = false;
+			char *var = arg, exp_save, exp_op, *exp_word;
+			size_t exp_off = 0;
 			*p = '\0';
 			arg[0] = first_ch & 0x7f;
-			if (isdigit(arg[0])) {
-				i = xatoi_u(arg);
+
+			/* prepare for expansions */
+			if (var[0] == '#') {
+				/* handle length expansion ${#var} */
+				exp_len = true;
+				++var;
+			} else {
+				/* maybe handle parameter expansion */
+				exp_off = strcspn(var, ":-=+?");
+				if (!var[exp_off])
+					exp_off = 0;
+				if (exp_off) {
+					exp_save = var[exp_off];
+					exp_null = exp_save == ':';
+					exp_word = var + exp_off;
+					if (exp_null) ++exp_word;
+					exp_op = *exp_word++;
+					var[exp_off] = '\0';
+				}
+			}
+
+			/* lookup the variable in question */
+			if (isdigit(var[0])) {
+				i = xatoi_u(var);
 				if (i < G.global_argc)
 					val = G.global_argv[i];
 				/* else val remains NULL: $N with too big N */
 			} else
-				val = lookup_param(arg);
+				val = lookup_param(var);
+
+			/* handle any expansions */
+			if (exp_len) {
+				debug_printf_expand("expand: length of '%s' = ", val);
+				val = utoa(val ? strlen(val) : 0);
+				debug_printf_expand("%s\n", val);
+			} else if (exp_off) {
+				/* we need to do an expansion */
+				int exp_test = (!val || (exp_null && !val[0]));
+				if (exp_op == '+')
+					exp_test = !exp_test;
+				debug_printf_expand("expand: op:%c (null:%s) test:%i\n", exp_op,
+					exp_null ? "true" : "false", exp_test);
+				if (exp_test) {
+					if (exp_op == '?')
+						maybe_die(var, *exp_word ? exp_word : "parameter null or not set");
+					else
+						val = exp_word;
+
+					if (exp_op == '=') {
+						if (isdigit(var[0]) || var[0] == '#') {
+							maybe_die(var, "special vars cannot assign in this way");
+							val = NULL;
+						} else {
+							char *new_var = xmalloc(strlen(var) + strlen(val) + 2);
+							sprintf(new_var, "%s=%s", var, val);
+							set_local_var(new_var, -1);
+						}
+					}
+				}
+				var[exp_off] = exp_save;
+			}
+
 			arg[0] = first_ch;
+
 #if ENABLE_HUSH_TICK
  store_val:
 #endif
-			*p = SPECIAL_VAR_SYMBOL;
 			if (!(first_ch & 0x80)) { /* unquoted $VAR */
 				debug_printf_expand("unquoted '%s', output->o_quote:%d\n", val, output->o_quote);
 				if (val) {
@@ -1643,9 +1714,13 @@
 				debug_printf_expand("quoted '%s', output->o_quote:%d\n", val, output->o_quote);
 			}
 		}
+		}
 		if (val) {
 			o_addQstr(output, val, strlen(val));
 		}
+		/* Do the check to avoid writing to a const string */
+		if (p && *p != SPECIAL_VAR_SYMBOL)
+			*p = SPECIAL_VAR_SYMBOL;
 
 #if ENABLE_HUSH_TICK
 		o_free(&subst_result);
@@ -3625,6 +3700,7 @@
 /* Return code: 0 for OK, 1 for syntax error */
 static int handle_dollar(o_string *dest, struct in_str *input)
 {
+	int expansion;
 	int ch = i_peek(input);  /* first character after the $ */
 	unsigned char quote_mask = dest->o_quote ? 0x80 : 0;
 
@@ -3658,25 +3734,71 @@
 		case '*': /* args */
 		case '@': /* args */
 			goto make_one_char_var;
-		case '{':
+		case '{': {
+			bool first_char;
+
 			o_addchr(dest, SPECIAL_VAR_SYMBOL);
 			i_getch(input);
 			/* XXX maybe someone will try to escape the '}' */
+			expansion = 0;
+			first_char = true;
 			while (1) {
 				ch = i_getch(input);
 				if (ch == '}')
 					break;
-				if (!isalnum(ch) && ch != '_') {
-					syntax("unterminated ${name}");
-					debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
-					return 1;
+
+				if (ch == '#' && first_char)
+					/* ${#var}: length of var contents */;
+
+				else if (expansion < 2 && !isalnum(ch) && ch != '_') {
+					/* handle parameter expansions
+					 * http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_06_02
+					 */
+					if (first_char)
+						goto case_default;
+					switch (ch) {
+						case ':': /* null modifier */
+							if (expansion == 0) {
+								debug_printf_parse(": null modifier\n");
+								++expansion;
+								break;
+							}
+							goto case_default;
+
+#if 0 /* not implemented yet :( */
+						case '#': /* remove prefix */
+						case '%': /* remove suffix */
+							if (expansion == 0) {
+								debug_printf_parse(": remove suffix/prefix\n");
+								expansion = 2;
+								break;
+							}
+							goto case_default;
+#endif
+
+						case '-': /* default value */
+						case '=': /* assign default */
+						case '+': /* alternative */
+						case '?': /* error indicate */
+							debug_printf_parse(": parameter expansion\n");
+							expansion = 2;
+							break;
+
+						default:
+						case_default:
+							syntax("unterminated ${name}");
+							debug_printf_parse("handle_dollar return 1: unterminated ${name}\n");
+							return 1;
+						}
 				}
 				debug_printf_parse(": '%c'\n", ch);
 				o_addchr(dest, ch | quote_mask);
 				quote_mask = 0;
+				first_char = false;
 			}
 			o_addchr(dest, SPECIAL_VAR_SYMBOL);
 			break;
+		}
 #if ENABLE_HUSH_TICK
 		case '(': {
 			//int pos = dest->length;