about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@zsh.org>2015-06-18 14:54:41 +0100
committerPeter Stephenson <pws@zsh.org>2015-06-19 09:50:29 +0100
commita29746ee48c84b40add764f3cbd62d995179f72a (patch)
tree9630693c7add01f6fbb6dd893c0bc233e824d169
parent98687fa1dec803f041cbb5417c146d8aa5129b53 (diff)
downloadzsh-a29746ee48c84b40add764f3cbd62d995179f72a.tar.gz
zsh-a29746ee48c84b40add764f3cbd62d995179f72a.tar.xz
zsh-a29746ee48c84b40add764f3cbd62d995179f72a.zip
First compiling attempt at typeset array handling.
Completely untested and undebugged.
-rw-r--r--Src/builtin.c234
-rw-r--r--Src/exec.c102
-rw-r--r--Src/hashtable.c4
-rw-r--r--Src/lex.c4
-rw-r--r--Src/parse.c122
-rw-r--r--Src/text.c57
-rw-r--r--Src/zsh.h57
7 files changed, 449 insertions, 131 deletions
diff --git a/Src/builtin.c b/Src/builtin.c
index 0edc07024..bd5d614ff 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -53,7 +53,7 @@ static struct builtin builtins[] =
     BUILTIN("cd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
     BUILTIN("chdir", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
     BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL),
-    BUILTIN("declare", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klmprtuxz", NULL),
+    BUILTIN("declare", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klmprtuxz", NULL),
     BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "clpv", NULL),
     BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmprs", NULL),
     BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL),
@@ -62,7 +62,7 @@ static struct builtin builtins[] =
     BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmprs", NULL),
     BUILTIN("eval", BINF_PSPECIAL, bin_eval, 0, -1, BIN_EVAL, NULL, NULL),
     BUILTIN("exit", BINF_PSPECIAL, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
-    BUILTIN("export", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "E:%F:%HL:%R:%TUZ:%afhi:%lprtu", "xg"),
+    BUILTIN("export", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, BIN_EXPORT, "E:%F:%HL:%R:%TUZ:%afhi:%lprtu", "xg"),
     BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL),
     /*
      * We used to behave as if the argument to -e was optional.
@@ -71,7 +71,7 @@ static struct builtin builtins[] =
      */
     BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "aAdDe:EfiIlLmnpPrRt:W", NULL),
     BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
-    BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlprtux", "E"),
+    BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlprtux", "E"),
     BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMtTuUx:z", NULL),
     BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
     BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
@@ -82,11 +82,11 @@ static struct builtin builtins[] =
 #endif
 
     BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "adDEfiLmnpPrt:", "l"),
-    BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "HL:%R:%Z:%ghi:%lprtux", "i"),
+    BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "HL:%R:%Z:%ghi:%lprtux", "i"),
     BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL),
     BUILTIN("kill", BINF_HANDLES_OPTS, bin_kill, 0, -1, 0, NULL, NULL),
     BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL),
-    BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%ahi:%lprtux", NULL),
+    BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%ahi:%lprtux", NULL),
     BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
     BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
 
@@ -106,7 +106,7 @@ static struct builtin builtins[] =
     BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
     BUILTIN("r", 0, bin_fc, 0, -1, BIN_R, "IlLnr", NULL),
     BUILTIN("read", 0, bin_read, 0, -1, 0, "cd:ek:%lnpqrst:%zu:AE", NULL),
-    BUILTIN("readonly", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%lptux", "r"),
+    BUILTIN("readonly", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%lptux", "r"),
     BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "df", "r"),
     BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL),
     BUILTIN("set", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_set, 0, -1, 0, NULL, NULL),
@@ -120,7 +120,7 @@ static struct builtin builtins[] =
     BUILTIN("trap", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_trap, 0, -1, 0, NULL, NULL),
     BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL),
     BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsSw", "v"),
-    BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klprtuxmz", NULL),
+    BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klprtuxmz", NULL),
     BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL),
     BUILTIN("unalias", 0, bin_unhash, 0, -1, BIN_UNALIAS, "ams", NULL),
     BUILTIN("unfunction", 0, bin_unhash, 1, -1, BIN_UNFUNCTION, "m", "f"),
@@ -246,7 +246,7 @@ new_optarg(Options ops)
 
 /**/
 int
-execbuiltin(LinkList args, Builtin bn)
+execbuiltin(LinkList args, LinkList assigns, Builtin bn)
 {
     char *pp, *name, *optstr;
     int flags, sense, argc, execop, xtr = isset(XTRACE);
@@ -447,7 +447,18 @@ execbuiltin(LinkList args, Builtin bn)
 	    fflush(xtrerr);
 	}
 	/* call the handler function, and return its return value */
-	return (*(bn->handlerfunc)) (name, argv, &ops, bn->funcid);
+	if (flags & BINF_ASSIGN)
+	{
+	    /*
+	     * Takes two sets of arguments.
+	     */
+	    HandlerFuncAssign assignfunc = (HandlerFuncAssign)bn->handlerfunc;
+	    return (*(assignfunc)) (name, argv, assigns, &ops, bn->funcid);
+	}
+	else
+	{
+	    return (*(bn->handlerfunc)) (name, argv, &ops, bn->funcid);
+	}
     }
 }
 
@@ -1452,12 +1463,13 @@ bin_fc(char *nam, char **argv, Options ops, int func)
 	if (!asgf)
 	    asgf = asgl = a;
 	else {
-	    asgl->next = a;
+	    asgl->node.next = &a->node;
 	    asgl = a;
 	}
 	a->name = *argv;
-	a->value = s;
-	a->next = NULL;
+	a->is_array = 0;
+	a->value.scalar = s;
+	a->node.next = a->node.prev = NULL;
 	argv++;
     }
     /* interpret and check first history line specifier */
@@ -1631,8 +1643,8 @@ fcsubs(char **sp, struct asgment *sub)
     /* loop through the linked list */
     while (sub) {
 	oldstr = sub->name;
-	newstr = sub->value;
-	sub = sub->next;
+	newstr = sub->value.scalar;
+	sub = (Asgment)sub->node.next;
 	oldpos = s;
 	/* loop over occurences of oldstr in s, replacing them with newstr */
 	while ((newpos = (char *)strstr(oldpos, oldstr))) {
@@ -1820,13 +1832,22 @@ fcedit(char *ename, char *fn)
 
 /**/
 static Asgment
-getasg(char *s)
+getasg(char ***argvp, LinkList assigns)
 {
+    char *s = **argvp;
     static struct asgment asg;
 
     /* sanity check for valid argument */
-    if (!s)
+    if (!s) {
+	if (assigns) {
+	    Asgment asgp = (Asgment)firstnode(assigns);
+	    if (!asgp)
+		return NULL;
+	    (void)uremnode(assigns, &asgp->node);
+	    return asgp;
+	}
 	return NULL;
+    }
 
     /* check if name is empty */
     if (*s == '=') {
@@ -1834,6 +1855,7 @@ getasg(char *s)
 	return NULL;
     }
     asg.name = s;
+    asg.is_array = 0;
 
     /* search for `=' */
     for (; *s && *s != '='; s++);
@@ -1841,11 +1863,12 @@ getasg(char *s)
     /* found `=', so return with a value */
     if (*s) {
 	*s = '\0';
-	asg.value = s + 1;
+	asg.value.scalar = s + 1;
     } else {
 	/* didn't find `=', so we only have a name */
-	asg.value = NULL;
+	asg.value.scalar = NULL;
     }
+    (*argvp)++;
     return &asg;
 }
 
@@ -1927,7 +1950,7 @@ typeset_setwidth(const char * name, Param pm, Options ops, int on, int always)
 /**/
 static Param
 typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
-	       int on, int off, int roff, char *value, Param altpm,
+	       int on, int off, int roff, Asgment asg, Param altpm,
 	       Options ops, int joinchar)
 {
     int usepm, tc, keeplocal = 0, newspecial = NS_NONE, readonly;
@@ -1975,7 +1998,23 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 
     /* attempting a type conversion, or making a tied colonarray? */
     tc = 0;
-    if (usepm || newspecial != NS_NONE) {
+    if (ASG_ARRAYP(asg))
+	on |= PM_ARRAY;
+    if (usepm && ASG_ARRAYP(asg) && newspecial == NS_NONE &&
+	PM_TYPE(pm->node.flags) != PM_ARRAY &&
+	PM_TYPE(pm->node.flags) != PM_HASHED) {
+	if (on & (PM_EFLOAT|PM_FFLOAT|PM_INTEGER)) {
+	    zerrnam(cname, "%s: can't assign array value to non-array", pname);
+	    return NULL;
+	}
+	if (pm->node.flags & PM_SPECIAL) {
+	    zerrnam(cname, "%s: can't assign array value to non-array special", pname);
+	    return NULL;
+	}
+	tc = 1;
+	usepm = 0;
+    }
+    else if (usepm || newspecial != NS_NONE) {
 	int chflags = ((off & pm->node.flags) | (on & ~pm->node.flags)) &
 	    (PM_INTEGER|PM_EFLOAT|PM_FFLOAT|PM_HASHED|
 	     PM_ARRAY|PM_TIED|PM_AUTOLOAD);
@@ -2023,7 +2062,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 			tc = 0;	/* but don't do a normal conversion */
 		    }
 		} else if (!setsecondstype(pm, on, off)) {
-		    if (value && !(pm = setsparam(pname, ztrdup(value))))
+		    if (asg->value.scalar && !(pm = setsparam(pname, ztrdup(asg->value.scalar))))
 			return NULL;
 		    usepm = 1;
 		    err = 0;
@@ -2049,7 +2088,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	 * Stricter rules about retaining readonly attribute in this case.
 	 */
 	if ((on & PM_READONLY) && (!usepm || (pm->node.flags & PM_UNSET)) &&
-	    !value)
+	    !ASG_VALUEP(asg))
 	    on |= PM_UNSET;
 	else if (usepm && (pm->node.flags & PM_READONLY) &&
 		 !(on & PM_READONLY)) {
@@ -2069,7 +2108,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
      */
     if (usepm) {
 	on &= ~PM_LOCAL;
-	if (!on && !roff && !value) {
+	if (!on && !roff && !ASG_VALUEP(asg)) {
 	    if (OPT_ISSET(ops,'p'))
 		paramtab->printnode(&pm->node, PRINT_TYPESET);
 	    else if (!OPT_ISSET(ops,'g') &&
@@ -2123,15 +2162,17 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	}
 	if (!(pm->node.flags & (PM_ARRAY|PM_HASHED))) {
 	    if (pm->node.flags & PM_EXPORTED) {
-		if (!(pm->node.flags & PM_UNSET) && !pm->env && !value)
+		if (!(pm->node.flags & PM_UNSET) && !pm->env && !ASG_VALUEP(asg))
 		    addenv(pm, getsparam(pname));
 	    } else if (pm->env && !(pm->node.flags & PM_HASHELEM))
 		delenv(pm);
-	    if (value && !(pm = setsparam(pname, ztrdup(value))))
+	    DPUTS(ASG_ARRAYP(asg), "BUG: typeset got array value where scalar expected");
+	    if (asg->value.scalar && !(pm = setsparam(pname, ztrdup(asg->value.scalar))))
+		return NULL;
+	} else if (asg->value.array) {
+	    DPUTS(!ASG_ARRAYP(asg), "BUG: typeset got scalar value where array expected");
+	    if (!(pm = setaparam(pname, zlinklist2array(asg->value.array))))
 		return NULL;
-	} else if (value) {
-	    zwarnnam(cname, "can't assign new value for array %s", pname);
-	    return NULL;
 	}
 	pm->node.flags |= (on & PM_READONLY);
 	if (OPT_ISSET(ops,'p'))
@@ -2158,9 +2199,14 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	/*
 	 * Try to carry over a value, but not when changing from,
 	 * to, or between non-scalar types.
+	 *
+	 * (We can do better now, but it does have user-visible
+	 * implications.)
 	 */
-	if (!value && !((pm->node.flags|on) & (PM_ARRAY|PM_HASHED)))
-	    value = dupstring(getsparam(pname));
+	if (!ASG_VALUEP(asg) && !((pm->node.flags|on) & (PM_ARRAY|PM_HASHED))) {
+	    asg->value.scalar = dupstring(getsparam(pname));
+	    asg->is_array = 0;
+	}
 	/* pname may point to pm->nam which is about to disappear */
 	pname = dupstring(pname);
 	unsetparam_pm(pm, 0, 1);
@@ -2251,16 +2297,17 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 		return NULL;
 	    }
 	}
-	if (PM_TYPE(on) == PM_SCALAR) {
+	if (PM_TYPE(on) == PM_SCALAR && !ASG_ARRAYP(asg)) {
 	    /*
 	     * This will either complain about bad identifiers, or will set
 	     * a hash element or array slice.  This once worked by accident,
 	     * creating a stray parameter along the way via createparam(),
 	     * now called below in the isident() branch.
 	     */
-	    if (!(pm = setsparam(pname, ztrdup(value ? value : ""))))
+	    if (!(pm = setsparam(pname, ztrdup(asg->value.scalar ? asg->value.scalar : ""))))
 		return NULL;
-	    value = NULL;
+	    asg->value.scalar = NULL;
+	    asg->is_array = 0;
 	    keeplocal = 0;
 	    on = pm->node.flags;
 	} else {
@@ -2331,9 +2378,10 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	pm->level = keeplocal;
     else if (on & PM_LOCAL)
 	pm->level = locallevel;
-    if (value && !(pm->node.flags & (PM_ARRAY|PM_HASHED))) {
+    if (ASG_VALUEP(asg) && !(pm->node.flags & (PM_ARRAY|PM_HASHED))) {
 	Param ipm = pm;
-	if (!(pm = setsparam(pname, ztrdup(value))))
+	DPUTS(ASG_ARRAYP(asg), "BUG: inconsistent array value for scalar");
+	if (!(pm = setsparam(pname, ztrdup(asg->value.scalar))))
 	    return NULL;
 	if (pm != ipm) {
 	    DPUTS(ipm->node.flags != pm->node.flags,
@@ -2371,11 +2419,10 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	}
     }
     pm->node.flags |= (on & PM_READONLY);
-    if (value && (pm->node.flags & (PM_ARRAY|PM_HASHED))) {
-	zerrnam(cname, "%s: can't assign initial value for array", pname);
-	/* the only safe thing to do here seems to be unset the param */
-	unsetparam_pm(pm, 0, 1);
-	return NULL;
+    if (ASG_VALUEP(asg) && (pm->node.flags & (PM_ARRAY|PM_HASHED))) {
+	DPUTS(!ASG_ARRAYP(asg), "BUG: inconsistent scalar value for array");
+	if (!(pm=setaparam(pname, zlinklist2array(asg->value.array))))
+	    return NULL;
     }
 
     if (OPT_ISSET(ops,'p'))
@@ -2384,11 +2431,18 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
     return pm;
 }
 
-/* declare, export, integer, local, readonly, typeset */
+/*
+ * declare, export, float, integer, local, readonly, typeset
+ *
+ * Note the difference in interface from most builtins, covered by the
+ * BINF_ASSIGN builtin flag.  This is only made use of by builtins
+ * called by reserved word, which only covers declare, local, readonly
+ * and typeset.  Otherwise assigns is NULL.
+ */
 
 /**/
 int
-bin_typeset(char *name, char **argv, Options ops, int func)
+bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 {
     Param pm;
     Asgment asg;
@@ -2469,7 +2523,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
     if (on & PM_TIED) {
 	Param apm;
 	struct asgment asg0;
-	char *oldval = NULL;
+	char *oldval = NULL, *joinstr;
 	int joinchar;
 
 	if (OPT_ISSET(ops,'m')) {
@@ -2484,28 +2538,29 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	    return 1;
 	}
 
-	/*
-	 * Third argument, if given, is character used to join
-	 * the elements of the array in the scalar.
-	 */
-	if (!argv[2])
-	    joinchar = ':';
-	else if (!*argv[2])
-	    joinchar = 0;
-	else if (*argv[2] == Meta)
-	    joinchar = argv[2][1] ^ 32;
-	else
-	    joinchar = *argv[2];
-
-	if (!(asg = getasg(argv[0]))) {
+	if (!(asg = getasg(&argv, assigns))) {
 	    unqueue_signals();
 	    return 1;
 	}
 	asg0 = *asg;
-	if (!(asg = getasg(argv[1]))) {
+	if (ASG_ARRAYP(&asg0)) {
+	    unqueue_signals();
+	    zwarnnam(name, "first argument of tie must be scalar: %s",
+		     asg0.name);
+	    return 1;
+	}
+
+	if (!(asg = getasg(&argv, assigns))) {
+	    unqueue_signals();
+	    return 1;
+	}
+	if (!ASG_ARRAYP(asg) && asg->value.scalar) {
 	    unqueue_signals();
+	    zwarnnam(name, "second argument of tie must be scalar: %s",
+		     asg->name);
 	    return 1;
 	}
+
 	if (!strcmp(asg0.name, asg->name)) {
 	    unqueue_signals();
 	    zerrnam(name, "can't tie a variable to itself: %s", asg0.name);
@@ -2516,6 +2571,31 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	    zerrnam(name, "can't tie array elements: %s", asg0.name);
 	    return 1;
 	}
+
+	/*
+	 * Third argument, if given, is character used to join
+	 * the elements of the array in the scalar.
+	 */
+	if (*argv)
+	    joinstr = *argv;
+	else if (assigns) {
+	    Asgment nextasg = (Asgment)firstnode(assigns);
+	    if (ASG_ARRAYP(nextasg) || ASG_VALUEP(nextasg)) {
+		zwarnnam(name, "third argument of tie must be join character");
+		unqueue_signals();
+		return 1;
+	    }
+	    joinstr = nextasg->name;
+	} else
+	    joinstr = NULL;
+	if (!joinstr)
+	    joinchar = ':';
+	else if (!*joinstr)
+	    joinchar = 0;
+	else if (*joinstr == Meta)
+	    joinchar = joinstr[1] ^ 32;
+	else
+	    joinchar = *joinstr;
 	/*
 	 * Keep the old value of the scalar.  We need to do this
 	 * here as if it is already tied to the same array it
@@ -2537,8 +2617,8 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 		    struct tieddata *tdp = (struct tieddata*)pm->u.data;
 		    /* Update join character */
 		    tdp->joinchar = joinchar;
-		    if (asg0.value)
-			setsparam(asg0.name, ztrdup(asg0.value));
+		    if (asg0.value.scalar)
+			setsparam(asg0.name, ztrdup(asg0.value.scalar));
 		    return 0;
 		} else {
 		    zwarnnam(name, "can't tie already tied scalar: %s",
@@ -2546,7 +2626,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 		}
 		return 1;
 	    }
-	    if (!asg0.value && !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)))
+	    if (!asg0.value.scalar && !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)))
 		oldval = ztrdup(getsparam(asg0.name));
 	    on |= (pm->node.flags & PM_EXPORTED);
 	}
@@ -2559,7 +2639,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 				 (Param)paramtab->getnode(paramtab,
 							  asg->name),
 				 func, (on | PM_ARRAY) & ~PM_EXPORTED,
-				 off, roff, asg->value, NULL, ops, 0))) {
+				 off, roff, asg, NULL, ops, 0))) {
 	    if (oldval)
 		zsfree(oldval);
 	    unqueue_signals();
@@ -2572,7 +2652,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	if (!(pm=typeset_single(name, asg0.name,
 				(Param)paramtab->getnode(paramtab,
 							 asg0.name),
-				func, on, off, roff, asg0.value, apm,
+				func, on, off, roff, &asg0, apm,
 				ops, joinchar))) {
 	    if (oldval)
 		zsfree(oldval);
@@ -2611,7 +2691,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 		printflags |= PRINT_NAMEONLY;
 	}
 
-	while ((asg = getasg(*argv++))) {
+	while ((asg = getasg(&argv, assigns))) {
 	    LinkList pmlist = newlinklist();
 	    LinkNode pmnode;
 
@@ -2622,7 +2702,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 		returnval = 1;
 		continue;
 	    }
-	    if (OPT_PLUS(ops,'m') && !asg->value) {
+	    if (OPT_PLUS(ops,'m') && !ASG_VALUEP(asg)) {
 		scanmatchtable(paramtab, pprog, 1, on|roff, 0,
 			       paramtab->printnode, printflags);
 		continue;
@@ -2648,7 +2728,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	    for (pmnode = firstnode(pmlist); pmnode; incnode(pmnode)) {
 		pm = (Param) getdata(pmnode);
 		if (!typeset_single(name, pm->node.nam, pm, func, on, off, roff,
-				    asg->value, NULL, ops, 0))
+				    asg, NULL, ops, 0))
 		    returnval = 1;
 	    }
 	}
@@ -2657,7 +2737,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
     }
 
     /* Take arguments literally.  Don't glob */
-    while ((asg = getasg(*argv++))) {
+    while ((asg = getasg(&argv, assigns))) {
 	HashNode hn = (paramtab == realparamtab ?
 		       gethashnode2(paramtab, asg->name) :
 		       paramtab->getnode(paramtab, asg->name));
@@ -2671,7 +2751,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	    continue;
 	}
 	if (!typeset_single(name, asg->name, (Param)hn,
-			    func, on, off, roff, asg->value, NULL,
+			    func, on, off, roff, asg, NULL,
 			    ops, 0))
 	    returnval = 1;
     }
@@ -3514,7 +3594,7 @@ bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
     }
 
     queue_signals();
-    for (;*argv;++argv) {
+    while (*argv) {
 	void *hn;
 	if (OPT_ISSET(ops,'m')) {
 	    /* with the -m option, treat the argument as a glob pattern */
@@ -3529,12 +3609,12 @@ bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
 	    }
             continue;
 	}
-        if (!(asg = getasg(*argv))) {
+        if (!(asg = getasg(&argv, NULL))) {
 	    zwarnnam(name, "bad assignment");
 	    returnval = 1;
-        } else if (asg->value) {
+        } else if (ASG_VALUEP(asg)) {
 	    if(isset(RESTRICTED)) {
-		zwarnnam(name, "restricted: %s", asg->value);
+		zwarnnam(name, "restricted: %s", asg->value.scalar);
 		returnval = 1;
 	    } else {
 		/* The argument is of the form foo=bar, *
@@ -3550,12 +3630,12 @@ bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
 		    } else {
 			Nameddir nd = hn = zshcalloc(sizeof *nd);
 			nd->node.flags = 0;
-			nd->dir = ztrdup(asg->value);
+			nd->dir = ztrdup(asg->value.scalar);
 		    }
 		} else {
 		    Cmdnam cn = hn = zshcalloc(sizeof *cn);
 		    cn->node.flags = HASHED;
-		    cn->u.cmd = ztrdup(asg->value);
+		    cn->u.cmd = ztrdup(asg->value.scalar);
 		}
 		ht->addnode(ht, ztrdup(asg->name), hn);
 		if(OPT_ISSET(ops,'v'))
@@ -3761,12 +3841,12 @@ bin_alias(char *name, char **argv, Options ops, UNUSED(int func))
 
     /* Take arguments literally.  Don't glob */
     queue_signals();
-    while ((asg = getasg(*argv++))) {
-	if (asg->value && !OPT_ISSET(ops,'L')) {
+    while ((asg = getasg(&argv, NULL))) {
+	if (asg->value.scalar && !OPT_ISSET(ops,'L')) {
 	    /* The argument is of the form foo=bar and we are not *
 	     * forcing a listing with -L, so define an alias      */
 	    ht->addnode(ht, ztrdup(asg->name),
-			createaliasnode(ztrdup(asg->value), flags1));
+			createaliasnode(ztrdup(asg->value.scalar), flags1));
 	} else if ((a = (Alias) ht->getnode(ht, asg->name))) {
 	    /* display alias if appropriate */
 	    if (!type_opts || ht == sufaliastab ||
diff --git a/Src/exec.c b/Src/exec.c
index 35a101b9c..74059bfdc 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2437,13 +2437,13 @@ execcmd(Estate state, int input, int output, int how, int last1)
     char *text;
     int save[10];
     int fil, dfil, is_cursh, type, do_exec = 0, redir_err = 0, i, htok = 0;
-    int nullexec = 0, assign = 0, forked = 0;
+    int nullexec = 0, assign = 0, forked = 0, postassigns = 0;
     int is_shfunc = 0, is_builtin = 0, is_exec = 0, use_defpath = 0;
     /* Various flags to the command. */
     int cflags = 0, orig_cflags = 0, checked = 0, oautocont = -1;
     LinkList redir;
     wordcode code;
-    Wordcode beg = state->pc, varspc;
+    Wordcode beg = state->pc, varspc, assignspc = (Wordcode)0;
     FILE *oxtrerr = xtrerr, *newxtrerr = NULL;
 
     doneps4 = 0;
@@ -2464,8 +2464,28 @@ execcmd(Estate state, int input, int output, int how, int last1)
     /* It would be nice if we could use EC_DUPTOK instead of EC_DUP here.
      * But for that we would need to check/change all builtins so that
      * they don't modify their argument strings. */
-    args = (type == WC_SIMPLE ?
-	    ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP, &htok) : NULL);
+    switch (type) {
+    case WC_SIMPLE:
+	args = ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP, &htok);
+	break;
+
+    case WC_TYPESET:
+	args = ecgetlist(state, WC_TYPESET_ARGC(code), EC_DUP, &htok);
+	postassigns = *state->pc++;
+	assignspc = state->pc;
+	for (i = 0; i < postassigns; i++) {
+	    code = *state->pc;
+	    DPUTS(wc_code(code) != WC_ASSIGN,
+		  "BUG: miscounted typeset assignments");
+	    state->pc += (WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR ?
+			  3 : WC_ASSIGN_NUM(code) + 2);
+	}
+	break;
+
+    default:
+	args = NULL;
+    }
+
     /*
      * If assignment but no command get the status from variable
      * assignment.
@@ -2488,7 +2508,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 
     /* If the command begins with `%', then assume it is a *
      * reference to a job in the job table.                */
-    if (type == WC_SIMPLE && args && nonempty(args) &&
+    if ((type == WC_SIMPLE || type == WC_TYPESET) && args && nonempty(args) &&
 	*(char *)peekfirst(args) == '%') {
         if (how & Z_DISOWN) {
 	    oautocont = opts[AUTOCONTINUE];
@@ -2517,7 +2537,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
      * command if it contains some tokens (e.g. x=ex; ${x}port), so this *
      * only works in simple cases.  has_token() is called to make sure   *
      * this really is a simple case.                                     */
-    if (type == WC_SIMPLE) {
+    if (type == WC_SIMPLE || type == WC_TYPESET) {
 	while (args && nonempty(args)) {
 	    char *cmdarg = (char *) peekfirst(args);
 	    checked = !has_token(cmdarg);
@@ -2661,7 +2681,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
     if (args && htok)
 	prefork(args, esprefork);
 
-    if (type == WC_SIMPLE) {
+    if (type == WC_SIMPLE || type == WC_TYPESET) {
 	int unglobbed = 0;
 
 	for (;;) {
@@ -2897,7 +2917,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 	return;
     }
 
-    if (type == WC_SIMPLE && !nullexec) {
+    if ((type == WC_SIMPLE || type == WC_TYPESET) && !nullexec) {
 	char *s;
 	char trycd = (isset(AUTOCD) && isset(SHINSTDIN) &&
 		      (!redir || empty(redir)) && args && !empty(args) &&
@@ -3457,9 +3477,71 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		execshfunc((Shfunc) hn, args);
 	    } else {
 		/* It's a builtin */
+		LinkList assigns = (LinkList)0;
 		if (forked)
 		    closem(FDT_INTERNAL);
-		lastval = execbuiltin(args, (Builtin) hn);
+		if (postassigns) {
+		    Wordcode opc = state->pc;
+		    state->pc = assignspc;
+		    assigns = newlinklist();
+		    while (postassigns--) {
+			Asgment asg = (Asgment)zhalloc(sizeof(struct asgment));
+			wordcode ac = *state->pc++;
+			int htok;
+			char *name = ecgetstr(state, EC_DUPTOK, &htok);
+			local_list1(svl);
+
+			DPUTS(wc_code(ac) != WC_ASSIGN,
+			      "BUG: bad assignment list for typeset");
+			if (htok)
+			    untokenize(name);
+			asg->name = name;
+			if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR) {
+			    char *val = ecgetstr(state, EC_DUPTOK, &htok);
+			    asg->is_array = 0;
+			    if (WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
+				/* Fake assignment, no value */
+				asg->value.scalar = NULL;
+			    } else {
+				if (htok) {
+				    init_list1(svl, val);
+				    prefork(&svl, PREFORK_SINGLE|PREFORK_ASSIGN);
+				    if (errflag) {
+					state->pc = opc;
+					break;
+				    }
+				    /*
+				     * No globassign for typeset
+				     * arguments, thank you
+				     */
+				    val = empty(&svl) ? "" :
+					(char *)getdata(firstnode(&svl));
+				}
+				untokenize(val);
+				asg->value.scalar = val;
+			    }
+			} else {
+			    asg->is_array = 1;
+			    asg->value.array =
+				ecgetlist(state, WC_ASSIGN_NUM(ac),
+					  EC_DUPTOK, &htok);
+			    prefork(asg->value.array, PREFORK_ASSIGN);
+			    if (errflag) {
+				state->pc = opc;
+				break;
+			    }
+			    globlist(asg->value.array, 0);
+			    if (errflag) {
+				state->pc = opc;
+				break;
+			    }
+			}
+
+			uaddlinknode(assigns, &asg->node);
+		    }
+		    state->pc = opc;
+		}
+		lastval = execbuiltin(args, assigns, (Builtin) hn);
 		fflush(stdout);
 		if (save[1] == -2) {
 		    if (ferror(stdout)) {
@@ -3501,7 +3583,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 		if (!subsh && isset(RCS) && interact && !nohistsave)
 		    savehistfile(NULL, 1, HFILE_USE_OPTIONS);
 	    }
-	    if (type == WC_SIMPLE) {
+	    if (type == WC_SIMPLE || type == WC_TYPESET) {
 		if (varspc) {
 		    int addflags = ADDVAR_EXPORT|ADDVAR_RESTRICT;
 		    if (forked)
diff --git a/Src/hashtable.c b/Src/hashtable.c
index 2b5524999..14c65e647 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -1050,6 +1050,7 @@ static struct reswd reswds[] = {
     {{NULL, "}", 0}, OUTBRACE},
     {{NULL, "case", 0}, CASE},
     {{NULL, "coproc", 0}, COPROC},
+    {{NULL, "declare", 0}, TYPESET},
     {{NULL, "do", 0}, DOLOOP},
     {{NULL, "done", 0}, DONE},
     {{NULL, "elif", 0}, ELIF},
@@ -1061,11 +1062,14 @@ static struct reswd reswds[] = {
     {{NULL, "foreach", 0}, FOREACH},
     {{NULL, "function", 0}, FUNC},
     {{NULL, "if", 0}, IF},
+    {{NULL, "local", 0}, TYPESET},
     {{NULL, "nocorrect", 0}, NOCORRECT},
+    {{NULL, "readonly", 0}, TYPESET},
     {{NULL, "repeat", 0}, REPEAT},
     {{NULL, "select", 0}, SELECT},
     {{NULL, "then", 0}, THEN},
     {{NULL, "time", 0}, TIME},
+    {{NULL, "typeset", 0}, TYPESET},
     {{NULL, "until", 0}, UNTIL},
     {{NULL, "while", 0}, WHILE},
     {{NULL, NULL, 0}, 0}
diff --git a/Src/lex.c b/Src/lex.c
index 841fb0b86..d56f670d2 100644
--- a/Src/lex.c
+++ b/Src/lex.c
@@ -1182,7 +1182,7 @@ gettokstr(int c, int sub)
 			c = Outpar;
 		    }
 		} else if (peek != ENVSTRING &&
-			   incmdpos && !bct && !brct) {
+			   (incmdpos || intypeset) && !bct && !brct) {
 		    char *t = tokstr;
 		    if (idigit(*t))
 			while (++t < lexbuf.ptr && idigit(*t));
@@ -1200,7 +1200,7 @@ gettokstr(int c, int sub)
 			t++;
 		    if (t == lexbuf.ptr) {
 			e = hgetc();
-			if (e == '(' && incmdpos) {
+			if (e == '(') {
 			    *lexbuf.ptr = '\0';
 			    return ENVARRAY;
 			}
diff --git a/Src/parse.c b/Src/parse.c
index c932851d9..b0273e254 100644
--- a/Src/parse.c
+++ b/Src/parse.c
@@ -63,6 +63,11 @@ int isnewlin;
 /**/
 int infor;
 
+/* != 0 if parsing arguments of typeset etc. */
+
+/**/
+int intypeset;
+
 /* list of here-documents */
 
 /**/
@@ -118,11 +123,22 @@ struct heredocs *hdocs;
  *   WC_ASSIGN
  *     - data contains type (scalar, array) and number of array-elements
  *     - followed by name and value
+ *     Note variant for WC_TYPESET assignments: WC_ASSIGN_INC indicates
+ *     a name with no equals, not an =+ which isn't valid here.
  *
  *   WC_SIMPLE
  *     - data contains the number of arguments (plus command)
  *     - followed by strings
  *
+ *   WC_TYPESET
+ *     Variant of WC_SIMPLE used when trailing assignments are
+ *     needed.  N.B.: if they are not, we use WC_SIMPLE even
+ *     if this is a TYPESET keyword.
+ *     - data contains the number of string arguments (plus command)
+ *     - followed by strings
+ *     - followed by number of assignments
+ *     - followed by assignments
+ *
  *   WC_SUBSH
  *     - data unused
  *     - followed by list
@@ -257,6 +273,7 @@ parse_context_save(struct parse_stack *ps, int toplevel)
     ps->incasepat = incasepat;
     ps->isnewlin = isnewlin;
     ps->infor = infor;
+    ps->intypeset = intypeset;
 
     ps->hdocs = hdocs;
     ps->eclen = eclen;
@@ -290,6 +307,7 @@ parse_context_restore(const struct parse_stack *ps, int toplevel)
     incasepat = ps->incasepat;
     isnewlin = ps->isnewlin;
     infor = ps->infor;
+    intypeset = ps->intypeset;
 
     hdocs = ps->hdocs;
     eclen = ps->eclen;
@@ -430,7 +448,7 @@ init_parse_status(void)
      * between the two it's a bit ambiguous.  We clear them when
      * using the lexical analyser for strings as well as here.
      */
-    incasepat = incond = inredir = infor = 0;
+    incasepat = incond = inredir = infor = intypeset = 0;
     incmdpos = 1;
 }
 
@@ -992,6 +1010,7 @@ par_cmd(int *cmplx, int zsh_construct)
     incmdpos = 1;
     incasepat = 0;
     incond = 0;
+    intypeset = 0;
     return 1;
 }
 
@@ -1709,7 +1728,8 @@ static int
 par_simple(int *cmplx, int nr)
 {
     int oecused = ecused, isnull = 1, r, argc = 0, p, isfunc = 0, sr = 0;
-    int c = *cmplx, nrediradd, assignments = 0;
+    int c = *cmplx, nrediradd, assignments = 0, ppost = 0;
+    wordcode postassigns = 0;
 
     r = ecused;
     for (;;) {
@@ -1717,31 +1737,32 @@ par_simple(int *cmplx, int nr)
 	    *cmplx = c = 1;
 	    nocorrect = 1;
 	} else if (tok == ENVSTRING) {
-	    char *p, *name, *str;
+	    char *ptr, *name, *str;
 
 	    name = tokstr;
-	    for (p = tokstr; *p && *p != Inbrack && *p != '=' && *p != '+';
-	         p++);
-	    if (*p == Inbrack) skipparens(Inbrack, Outbrack, &p);
-	    if (*p == '+') {
-	    	*p++ = '\0';
+	    for (ptr = tokstr;
+		 *ptr && *ptr != Inbrack && *ptr != '=' && *ptr != '+';
+	         ptr++);
+	    if (*ptr == Inbrack) skipparens(Inbrack, Outbrack, &ptr);
+	    if (*ptr == '+') {
+	    	*ptr++ = '\0';
 	    	ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
 	    } else
 		ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_NEW, 0));
-    	
-	    if (*p == '=') {
-		*p = '\0';
-		str = p + 1;
+
+	    if (*ptr == '=') {
+		*ptr = '\0';
+		str = ptr + 1;
 	    } else
 		equalsplit(tokstr, &str);
-	    for (p = str; *p; p++) {
+	    for (ptr = str; *ptr; ptr++) {
 		/*
 		 * We can't treat this as "simple" if it contains
 		 * expansions that require process subsitution, since then
 		 * we need process handling.
 		 */
-		if (p[1] == Inpar &&
-		    (*p == Equals || *p == Inang || *p == OutangProc)) {
+		if (ptr[1] == Inpar &&
+		    (*ptr == Equals || *ptr == Inang || *ptr == OutangProc)) {
 		    *cmplx = 1;
 		    break;
 		}
@@ -1786,14 +1807,18 @@ par_simple(int *cmplx, int nr)
     p = ecadd(WCB_SIMPLE(0));
 
     for (;;) {
-	if (tok == STRING) {
+	if (tok == STRING || tok == TYPESET) {
 	    int redir_var = 0;
 
 	    *cmplx = 1;
 	    incmdpos = 0;
 
+	    if (tok == TYPESET)
+		intypeset = 1;
+
 	    if (!isset(IGNOREBRACES) && *tokstr == Inbrace)
 	    {
+		/* Look for redirs of the form {var}>file etc. */
 		char *eptr = tokstr + strlen(tokstr) - 1;
 		char *ptr = eptr;
 
@@ -1824,15 +1849,62 @@ par_simple(int *cmplx, int nr)
 
 	    if (!redir_var)
 	    {
-		ecstr(tokstr);
-		argc++;
-		zshlex();
+		if (postassigns) {
+		    /*
+		     * We're in the variable part of a typeset,
+		     * but this doesn't have an assignment.
+		     * We'll parse it as if it does, but mark
+		     * it specially with WC_ASSIGN_INC.
+		     */
+		    postassigns++;
+		    ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
+		    ecstr(tokstr);
+		    ecstr("");	/* TBD can possibly optimise out */
+		} else {
+		    ecstr(tokstr);
+		    argc++;
+		    zshlex();
+		}
 	    }
 	} else if (IS_REDIROP(tok)) {
 	    *cmplx = c = 1;
 	    nrediradd = par_redir(&r, NULL);
 	    p += nrediradd;
 	    sr += nrediradd;
+	} else if (tok == ENVSTRING) {
+	    char *ptr, *name, *str;
+
+	    if (!postassigns++)
+		ppost = ecadd(0);
+
+	    name = tokstr;
+	    for (ptr = tokstr; *ptr && *ptr != Inbrack && *ptr != '=' && *ptr != '+';
+	         ptr++);
+	    if (*ptr == Inbrack) skipparens(Inbrack, Outbrack, &ptr);
+	    ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_NEW, 0));
+
+	    if (*ptr == '=') {
+		*ptr = '\0';
+		str = ptr + 1;
+	    } else
+		equalsplit(tokstr, &str);
+	    ecstr(name);
+	    ecstr(str);
+	} else if (tok == ENVARRAY) {
+	    int n, parr;
+
+	    if (!postassigns++)
+		ppost = ecadd(0);
+
+	    parr = ecadd(0);
+	    ecstr(tokstr);
+	    cmdpush(CS_ARRAY);
+	    zshlex();
+	    n = par_nl_wordlist();
+	    ecbuf[parr] = WCB_ASSIGN(WC_ASSIGN_ARRAY, WC_ASSIGN_NEW, n);
+	    cmdpop();
+	    if (tok != OUTPAR)
+		YYERROR(oecused);
 	} else if (tok == INOUTPAR) {
 	    zlong oldlineno = lineno;
 	    int onp, so, oecssub = ecssub;
@@ -1841,7 +1913,7 @@ par_simple(int *cmplx, int nr)
 	    if (!isset(MULTIFUNCDEF) && argc > 1)
 		YYERROR(oecused);
 	    /* Error if preceding assignments */
-	    if (assignments)
+	    if (assignments || postassigns)
 		YYERROR(oecused);
 
 	    *cmplx = c;
@@ -1947,9 +2019,15 @@ par_simple(int *cmplx, int nr)
 	return 0;
     }
     incmdpos = 1;
+    intypeset = 0;
 
-    if (!isfunc)
-	ecbuf[p] = WCB_SIMPLE(argc);
+    if (!isfunc) {
+	if (postassigns) {
+	    ecbuf[p] = WCB_TYPESET(argc);
+	    ecbuf[ppost] = postassigns;
+	} else
+	    ecbuf[p] = WCB_SIMPLE(argc);
+    }
 
     return sr + 1;
 }
diff --git a/Src/text.c b/Src/text.c
index 850879699..a72ab33e6 100644
--- a/Src/text.c
+++ b/Src/text.c
@@ -155,6 +155,46 @@ taddlist(Estate state, int num)
     }
 }
 
+/* add an assignment */
+
+static void
+taddassign(wordcode code, Estate state, int typeset)
+{
+    /* name */
+    taddstr(ecgetstr(state, EC_NODUP, NULL));
+    /* value... maybe */
+    if (WC_ASSIGN_TYPE2(code) == WC_ASSIGN_INC) {
+	if (typeset) {
+	    /* dummy assignment --- just var name */
+	    (void)ecgetstr(state, EC_NODUP, NULL);
+	    taddchr(' ');
+	    return;
+	}
+	taddchr('+');
+    }
+    taddchr('=');
+    if (WC_ASSIGN_TYPE(code) == WC_ASSIGN_ARRAY) {
+	taddchr('(');
+	taddlist(state, WC_ASSIGN_NUM(code));
+	taddstr(") ");
+    } else {
+	taddstr(ecgetstr(state, EC_NODUP, NULL));
+	taddchr(' ');
+    }
+}
+
+/* add a number of assignments from typeset */
+
+/**/
+static void
+taddassignlist(Estate state, wordcode count)
+{
+    while (count--) {
+	wordcode code = *state->pc++;
+	taddassign(code, state, 1);
+    }
+}
+
 /* add a newline, or something equivalent, to the text buffer */
 
 /**/
@@ -439,22 +479,17 @@ gettext2(Estate state)
 	    }
 	    break;
 	case WC_ASSIGN:
-	    taddstr(ecgetstr(state, EC_NODUP, NULL));
-	    if (WC_ASSIGN_TYPE2(code) == WC_ASSIGN_INC) taddchr('+');
-	    taddchr('=');
-	    if (WC_ASSIGN_TYPE(code) == WC_ASSIGN_ARRAY) {
-		taddchr('(');
-		taddlist(state, WC_ASSIGN_NUM(code));
-		taddstr(") ");
-	    } else {
-		taddstr(ecgetstr(state, EC_NODUP, NULL));
-		taddchr(' ');
-	    }
+	    taddassign(code, state, 0);
 	    break;
 	case WC_SIMPLE:
 	    taddlist(state, WC_SIMPLE_ARGC(code));
 	    stack = 1;
 	    break;
+	case WC_TYPESET:
+	    taddlist(state, WC_TYPESET_ARGC(code));
+	    taddassignlist(state, *state->pc++);
+	    stack = 1;
+	    break;
 	case WC_SUBSH:
 	    if (!s) {
 		taddstr("(");
diff --git a/Src/zsh.h b/Src/zsh.h
index fb04929d9..0d43d37a7 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -336,7 +336,8 @@ enum lextok {
     THEN,	/* then      */
     TIME,	/* time      */ /* 60 */
     UNTIL,	/* until     */
-    WHILE	/* while     */
+    WHILE,	/* while     */
+    TYPESET     /* typeset or similar */
 };
 
 /* Redirection types.  If you modify this, you may also have to modify *
@@ -671,14 +672,6 @@ struct multio {
     int fds[MULTIOUNIT];	/* list of src/dests redirected to/from this fd */
 };
 
-/* structure for foo=bar assignments */
-
-struct asgment {
-    struct asgment *next;
-    char *name;
-    char *value;
-};
-
 /* lvalue for variable assignment/expansion */
 
 struct value {
@@ -803,6 +796,7 @@ struct eccstr {
 #define WC_ARITH   18
 #define WC_AUTOFN  19
 #define WC_TRY     20
+#define WC_TYPESET 21
 
 /* increment as necessary */
 #define WC_COUNT   21
@@ -849,6 +843,12 @@ struct eccstr {
 #define WC_ASSIGN_SCALAR    0
 #define WC_ASSIGN_ARRAY     1
 #define WC_ASSIGN_NEW       0
+/*
+ * In normal assignment, this indicate += to append.
+ * In assignment following a typeset, where that's not allowed,
+ * we overload this to indicate a variable without an
+ * assignment.
+ */
 #define WC_ASSIGN_INC       1
 #define WC_ASSIGN_NUM(C)    (wc_data(C) >> 2)
 #define WCB_ASSIGN(T,A,N)   wc_bld(WC_ASSIGN, ((T) | ((A) << 1) | ((N) << 2)))
@@ -856,6 +856,9 @@ struct eccstr {
 #define WC_SIMPLE_ARGC(C)   wc_data(C)
 #define WCB_SIMPLE(N)       wc_bld(WC_SIMPLE, (N))
 
+#define WC_TYPESET_ARGC(C)  wc_data(C)
+#define WCB_TYPESET(N)      wc_bld(WC_TYPESET, (N))
+
 #define WC_SUBSH_SKIP(C)    wc_data(C)
 #define WCB_SUBSH(O)        wc_bld(WC_SUBSH, (O))
 
@@ -1140,6 +1143,34 @@ struct alias {
 /* is this an alias for suffix handling? */
 #define ALIAS_SUFFIX	(1<<2)
 
+/* structure for foo=bar assignments */
+
+struct asgment {
+    struct linknode node;
+    char *name;
+    int is_array;
+    union {
+	char *scalar;
+	LinkList array;
+    } value;
+};
+
+/*
+ * Assignment is array?
+ */
+#define ASG_ARRAYP(asg) ((asg)->is_array)
+
+/*
+ * Assignment has value?
+ * We need to arrange for each of the values
+ * to be the same type or the compiler will
+ * get fed up.
+ */
+
+#define ASG_VALUEP(asg) (ASG_ARRAYP(asg) ?			\
+			 ((asg)->value.array != (LinkList)0) :	\
+			 ((asg)->value.scalar != (char *)0))
+
 /* node in command path hash table (cmdnamtab) */
 
 struct cmdnam {
@@ -1268,6 +1299,7 @@ struct options {
  */
 
 typedef int (*HandlerFunc) _((char *, char **, Options, int));
+typedef int (*HandlerFuncAssign) _((char *, char **, LinkList, Options, int));
 #define NULLBINCMD ((HandlerFunc) 0)
 
 struct builtin {
@@ -1311,6 +1343,12 @@ struct builtin {
   * does not terminate options.
   */
 #define BINF_HANDLES_OPTS	(1<<18)
+/*
+ * Handles the assignement interface.  The argv list actually contains
+ * two nested litsts, the first of normal arguments, and the second of
+ * assignment structures.
+ */
+#define BINF_ASSIGN		(1<<19)
 
 struct module {
     struct hashnode node;
@@ -2779,6 +2817,7 @@ struct parse_stack {
     int incasepat;
     int isnewlin;
     int infor;
+    int intypeset;
 
     int eclen, ecused, ecnpats;
     Wordcode ecbuf;