about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog11
-rw-r--r--Config/version.mk4
-rw-r--r--Doc/Zsh/builtins.yo91
-rw-r--r--Doc/Zsh/grammar.yo3
-rw-r--r--Doc/Zsh/options.yo8
-rw-r--r--NEWS19
-rw-r--r--README17
-rw-r--r--Src/builtin.c309
-rw-r--r--Src/exec.c176
-rw-r--r--Src/hashtable.c7
-rw-r--r--Src/lex.c4
-rw-r--r--Src/options.c2
-rw-r--r--Src/parse.c131
-rw-r--r--Src/text.c57
-rw-r--r--Src/zsh.h87
-rw-r--r--Test/B02typeset.ztst145
-rw-r--r--Test/D01prompt.ztst2
-rw-r--r--Test/E01options.ztst11
18 files changed, 893 insertions, 191 deletions
diff --git a/ChangeLog b/ChangeLog
index 754a14083..0295d5a2b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,14 @@
+2015-06-24  Peter Stephenson  <p.stephenson@samsung.com>
+
+	* various culminating in 35586, c.f. commits on typeset-array
+	branch: Config/version.mk, Doc/Zsh/builtins.yo,
+	Doc/Zsh/grammar.yo, Doc/Zsh/options.yo, NEWS, README,
+	Src/builtin.c, Src/exec.c, Src/hashtable.c, Src/lex.c,
+	Src/options.c, Src/parse.c, Src/text.c, Src/zsh.h,
+	Test/B02typeset.ztst, Test/D01prompt.ztst, Test/E01options.ztst:
+	Implement assignment handling for typeset etc. when matched as
+	reserved words.  Document and test.
+
 2015-06-23  Peter Stephenson  <p.stephenson@samsung.com>
 
 	* 35573: Completion/compinit: turn off POSIX_BUILTINS
diff --git a/Config/version.mk b/Config/version.mk
index 1c89e9edb..6f6d1bc7d 100644
--- a/Config/version.mk
+++ b/Config/version.mk
@@ -27,5 +27,5 @@
 # This must also serve as a shell script, so do not add spaces around the
 # `=' signs.
 
-VERSION=5.0.8-dev-0
-VERSION_DATE='June 1, 2015'
+VERSION=5.0.8-dev-1
+VERSION_DATE='June 19, 2015'
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index 12a13caa0..33f112225 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -1729,7 +1729,7 @@ xitem(tt(typeset )[ {tt(PLUS())|tt(-)}tt(AHUaghlmprtux) ] \
 [ {tt(PLUS())|tt(-)}tt(EFLRZi) [ var(n) ] ])
 xitem(SPACES()[ tt(+) | var(name)[tt(=)var(value)] ... ])
 xitem(tt(typeset )tt(-T) [ {tt(PLUS())|tt(-)}tt(Uglprux) ] [ {tt(PLUS())|tt(-)}tt(LRZ) [ var(n) ] ])
-xitem(SPACES()[ tt(+) | var(SCALAR)[tt(=)var(value)] var(array) [ var(sep) ] ])
+xitem(SPACES()[ tt(+) | var(SCALAR)[tt(=)var(value)] var(array)[tt(=)LPAR()var(value)RPAR()] [ var(sep) ] ])
 item(tt(typeset) tt(-f) [ {tt(PLUS())|tt(-)}tt(TUkmtuz) ] [ tt(+) | var(name) ... ])(
 Set or display attributes and values for shell parameters.
 
@@ -1743,22 +1743,72 @@ ifnzman(noderef(Local Parameters))\
 retain their special attributes when made local.
 
 For each var(name)tt(=)var(value) assignment, the parameter
-var(name) is set to var(value).  Note that arrays currently cannot be
-assigned in tt(typeset) expressions, only scalars and integers.  Unless
-the option tt(KSH_TYPESET) is set, normal expansion rules apply to
-assignment arguments, so var(value) may be split into separate words; if
-the option is set, assignments which can be recognised when expansion is
-performed are treated as single words.  For example the command
-tt(typeset vbl=$(echo one two)) is treated as having one argument if
-tt(KSH_TYPESET) is set, but otherwise is treated as having the two arguments
-tt(vbl=one) and tt(two).
+var(name) is set to var(value).  All forms of the command
+handle scalar assignment.
+
+If any of the reserved words tt(declare), tt(export), tt(float),
+tt(integer), tt(local), tt(readonly) or tt(typeset) is matched when the
+line is parsed (N.B. not when it is executed) the shell will try to parse
+arguments as assignments, except that the `tt(+=)' syntax and the
+tt(GLOB_ASSIGN) option are not supported.  This has two major differences
+from normal command line argument parsing: array assignment is possible,
+and scalar values after tt(=) are not split further into words even if
+expanded (regardless of the setting of the tt(KSH_TYPESET) option; this
+option is obsolete).  Here is an example:
+
+example(# Reserved word parsing
+typeset svar=$(echo one word) avar=(several words))
+
+The above creates a scalar parameter tt(svar) and an array
+parameter tt(var) as if the assignments had been
+
+example(svar="one word"
+avar=(several words))
+
+On the other hand:
+
+example(# Normal builtin interface
+builtin typeset svar=$(echo two words))
+
+The tt(builtin) keyword causes the above to use the standard builtin
+interface to tt(typeset) in which argument parsing is perfomed in the same
+way as for other commands.  This example creates a scalar tt(svar)
+containing the value tt(two) and another scalar parameter tt(words) with
+no value.  An array value in this case would either cause an error or be
+treated as an obscure set of glob qualifiers.
+
+Arbitrary arguments are allowed if they take the form of assignments
+after command line expansion; however, these only perform scalar
+assignment:
+
+example(var='svar=val'
+typeset $var)
+
+The above sets the scalar parameter tt(svar) to the value tt(val).
+Parentheses around the value within tt(var) would not cause array
+assignment as they will be treated as ordinary characters when tt($var)
+is substituted.  Any non-trivial expansion in the name part of the
+assignment causes the argument to be treated in this fashion:
+
+example(typeset {var1,var2,var3}=name)
+
+The above syntax is valid, and has the expected effect of setting the
+three parameters to the same value, but the command line is parsed as
+a set of three normal command line arguments to tt(typeset) after
+expansion.  Hence it is not possible to assign to multiple arrays by
+this means.
+
+Note that each interface to any of the commands my be disabled
+separately.  For example, `tt(disable -r typeset)' disables the reserved
+word interface to tt(typeset), exposing the builtin interface, while
+`tt(disable typeset)' disables the builtin.
 
 If the shell option tt(TYPESET_SILENT) is not set, for each remaining
-var(name) that refers to a parameter that is set, the name and value of the
-parameter are printed in the form of an assignment.  Nothing is printed for
-newly-created parameters, or when any attribute flags listed below are
-given along with the var(name).  Using `tt(PLUS())' instead of minus to
-introduce an attribute turns it off.
+var(name) that refers to a parameter that is already set, the name and
+value of the parameter are printed in the form of an assignment.
+Nothing is printed for newly-created parameters, or when any attribute
+flags listed below are given along with the var(name).  Using
+`tt(PLUS())' instead of minus to introduce an attribute turns it off.
 
 If no var(name) is present, the names and values of all parameters are
 printed.  In this case the attribute flags restrict the display to only
@@ -1829,7 +1879,7 @@ the current state, readonly specials (whose values cannot be
 changed) are not shown and assignments to arrays are shown before
 the tt(typeset) rendering the array readonly.
 )
-item(tt(-T) [ var(scalar)[tt(=)var(value)] var(array) [ var(sep) ] ])(
+item(tt(-T) [ var(scalar)[tt(=)var(value)] var(array)[tt(=)LPAR()var(value)...RPAR()] [ var(sep) ] ])(
 This flag has a different meaning when used with tt(-f); see below.
 Otherwise the tt(-T) option requires zero, two, or three arguments to be
 present.  With no arguments, the list of parameters created in this
@@ -1839,10 +1889,13 @@ together in the manner of tt($PATH) and tt($path).  The optional third
 argument is a single-character separator which will be used to join the
 elements of the array to form the scalar; if absent, a colon is used, as
 with tt($PATH).  Only the first character of the separator is significant;
-any remaining characters are ignored.
+any remaining characters are ignored.  Multibyte characters are not
+yet supported.
 
-Only the scalar parameter may be assigned an initial value.  Both the
-scalar and the array may otherwise be manipulated as normal.  If one is
+Only one of the scalar and array parameters may be assigned an initial
+value (the restrictions on assignment forms described above also apply).
+
+Both the scalar and the array may be manipulated as normal.  If one is
 unset, the other will automatically be unset too.  There is no way of
 untying the variables without unsetting them, nor of converting the type
 of one of them with another tt(typeset) command; tt(+T) does not work,
diff --git a/Doc/Zsh/grammar.yo b/Doc/Zsh/grammar.yo
index 4476fc392..83968fedf 100644
--- a/Doc/Zsh/grammar.yo
+++ b/Doc/Zsh/grammar.yo
@@ -472,7 +472,8 @@ word of a command unless quoted or disabled using tt(disable -r):
 
 tt(do done esac then elif else fi for case
 if while function repeat time until
-select coproc nocorrect foreach end ! [[ { })
+select coproc nocorrect foreach end ! [[ { }
+declare export float integer local readonly typeset)
 
 Additionally, `tt(})' is recognized in any position if neither the
 tt(IGNORE_BRACES) option nor the tt(IGNORE_CLOSE_BRACES) option is set.
diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo
index db9b18b8c..b4a5e105f 100644
--- a/Doc/Zsh/options.yo
+++ b/Doc/Zsh/options.yo
@@ -1928,7 +1928,13 @@ pindex(KSHTYPESET)
 pindex(NOKSHTYPESET)
 cindex(argument splitting, in typeset etc.)
 cindex(ksh, argument splitting in typeset)
-item(tt(KSH_TYPESET) <K>)(
+item(tt(KSH_TYPESET))(
+This option is now obsolete: a better appropximation to the behaviour of
+other shells is obtained with the reserved word interface to
+tt(declare), tt(export), tt(float), tt(integer), tt(local), tt(readonly)
+and tt(typeset).  Note that the option is only applied when the reserved
+word interface is em(not) in use.
+
 Alters the way arguments to the tt(typeset) family of commands, including
 tt(declare), tt(export), tt(float), tt(integer), tt(local) and
 tt(readonly), are processed.  Without this option, zsh will perform normal
diff --git a/NEWS b/NEWS
index 44bf6b9c0..d515a6036 100644
--- a/NEWS
+++ b/NEWS
@@ -4,7 +4,21 @@ CHANGES FROM PREVIOUS VERSIONS OF ZSH
 
 Note also the list of incompatibilities in the README file.
 
-Changes from 5.0.7 to 5.0.8
+Changes from 5.0.8 to 5.0.9
+---------------------------
+
+The builtins declare, export, local, readonly and typeset
+now have corresponding reserved words.  When used in
+this form, the builtin syntax is extended so that assignments
+following the reserved word are treated similarly to
+assignments that appear at the start of the command line.
+For example,
+  local scalar=`echo one word` array=(several words)
+creates a local "scalar" containing the text "one word"
+and an array "array" containing the words "several"
+"words".
+
+Changes from 5.0.0 to 5.0.8
 ---------------------------
 
 - Global aliases can be created for syntactic tokens such as command
@@ -47,9 +61,6 @@ Changes from 5.0.7 to 5.0.8
 - Some rationalisations have been made to the zsh/db/gdbm module that
   should make it more useful and predictable in operation.
 
-Changes from 5.0.0 to 5.0.7
----------------------------
-
 - Numeric constants encountered in mathematical expressions (but not other
   contexts) can contain underscores as separators that will be ignored on
   evaluation, as allowed in other scripting languages.  For example,
diff --git a/README b/README
index 10f29a4ed..745ee6fce 100644
--- a/README
+++ b/README
@@ -30,6 +30,23 @@ Zsh is a shell with lots of features.  For a list of some of these, see the
 file FEATURES, and for the latest changes see NEWS.  For more
 details, see the documentation.
 
+Incompatibilites between 5.0.8 and 5.0.9
+----------------------------------------
+
+As noted in NEWS, the builtins declare, export, float, integer, local,
+readonly and typeset now have corresponding reserved words that provide
+true assignment semantics instead of an approximation by means of normal
+command line arguments.  It is hoped that this additional consistency
+provides a more natural interface.  However, compatbility with older
+versions of zsh can be obtained by turning off the reserved word
+interface, exposing the builtin interface:
+
+  disable -r declare export float integer local readonly typeset
+
+This is also necessary in the unusual eventuality that the builtins are
+to be overridden by shell functions, since reserved words take
+precedence over functions.
+
 Incompatibilites between 5.0.7 and 5.0.8
 ----------------------------------------
 
diff --git a/Src/builtin.c b/Src/builtin.c
index 0edc07024..ba384068d 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);
@@ -443,11 +443,44 @@ execbuiltin(LinkList args, Builtin bn)
 	        fputc(' ', xtrerr);
 	        quotedzputs(*fullargv++, xtrerr);
 	    }
+	    if (assigns) {
+		LinkNode node;
+		for (node = firstnode(assigns); node; incnode(node)) {
+		    Asgment asg = (Asgment)node;
+		    fputc(' ', xtrerr);
+		    quotedzputs(asg->name, xtrerr);
+		    if (asg->is_array) {
+			LinkNode arrnode;
+			fprintf(xtrerr, "=(");
+			for (arrnode = firstnode(asg->value.array);
+			     arrnode;
+			     incnode(arrnode)) {
+			    fputc(' ', xtrerr);
+			    quotedzputs((char *)getdata(arrnode), xtrerr);
+			}
+			fprintf(xtrerr, " )");
+		    } else if (asg->value.scalar) {
+			fputc('=', xtrerr);
+			quotedzputs(asg->value.scalar, xtrerr);
+		    }
+		}
+	    }
 	    fputc('\n', xtrerr);
 	    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 +1485,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 +1665,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 +1854,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 +1877,7 @@ getasg(char *s)
 	return NULL;
     }
     asg.name = s;
+    asg.is_array = 0;
 
     /* search for `=' */
     for (; *s && *s != '='; s++);
@@ -1841,11 +1885,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 +1972,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 +2020,24 @@ 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) && PM_TYPE(on) == PM_SCALAR &&
+	!(usepm && (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED))))
+	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 +2085,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 +2111,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)) {
@@ -2068,8 +2130,14 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
      *   ii. we are creating a new local parameter
      */
     if (usepm) {
+	if (asg->is_array ?
+	    (asg->value.array && !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED))) :
+	    (asg->value.scalar && (PM_TYPE(pm->node.flags & (PM_ARRAY|PM_HASHED))))) {
+	    zerrnam(cname, "%s: inconsistent type for assignment", pname);
+	    return NULL;
+	}
 	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 +2191,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'))
@@ -2139,6 +2209,13 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	return pm;
     }
 
+    if (asg->is_array ?
+	(asg->value.array && !(on & (PM_ARRAY|PM_HASHED))) :
+	(asg->value.scalar && (on & (PM_ARRAY|PM_HASHED)))) {
+	zerrnam(cname, "%s: inconsistent type for assignment", pname);
+	return NULL;
+    }
+
     /*
      * We're here either because we're creating a new parameter,
      * or we're adding a parameter at a different local level,
@@ -2158,9 +2235,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 +2333,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,10 +2414,17 @@ 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)) {
 	Param ipm = pm;
-	if (!(pm = setsparam(pname, ztrdup(value))))
-	    return NULL;
+	if (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;
+	} else {
+	    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,
 		  "BUG: parameter recreated with wrong flags");
@@ -2371,12 +2461,6 @@ 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 (OPT_ISSET(ops,'p'))
 	paramtab->printnode(&pm->node, PRINT_TYPESET);
@@ -2384,11 +2468,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;
@@ -2397,6 +2488,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
     int on = 0, off = 0, roff, bit = PM_ARRAY;
     int i;
     int returnval = 0, printflags = 0;
+    int hasargs;
 
     /* hash -f is really the builtin `functions' */
     if (OPT_ISSET(ops,'f'))
@@ -2449,7 +2541,8 @@ bin_typeset(char *name, char **argv, Options ops, int func)
     /* Given no arguments, list whatever the options specify. */
     if (OPT_ISSET(ops,'p'))
 	printflags |= PRINT_TYPESET;
-    if (!*argv) {
+    hasargs = *argv != NULL || (assigns && firstnode(assigns));
+    if (!hasargs) {
 	if (!OPT_ISSET(ops,'p')) {
 	    if (!(on|roff))
 		printflags |= PRINT_TYPE;
@@ -2468,9 +2561,9 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 
     if (on & PM_TIED) {
 	Param apm;
-	struct asgment asg0;
-	char *oldval = NULL;
-	int joinchar;
+	struct asgment asg0, asg2;
+	char *oldval = NULL, *joinstr;
+	int joinchar, nargs;
 
 	if (OPT_ISSET(ops,'m')) {
 	    zwarnnam(name, "incompatible options for -T");
@@ -2478,34 +2571,41 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	    return 1;
 	}
 	on &= ~off;
-	if (!argv[1] || (argv[2] && argv[3])) {
+	nargs = arrlen(argv) + (assigns ? countlinknodes(assigns) : 0);
+	if (nargs < 2) {
 	    zwarnnam(name, "-T requires names of scalar and array");
 	    unqueue_signals();
 	    return 1;
 	}
+	if (nargs > 3) {
+	    zwarnnam(name, "too many arguments for -T");
+	    unqueue_signals();
+	    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 array: %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 +2616,36 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	    zerrnam(name, "can't tie array elements: %s", asg0.name);
 	    return 1;
 	}
+	if (ASG_VALUEP(asg) && ASG_VALUEP(&asg0)) {
+	    unqueue_signals();
+	    zerrnam(name, "only one tied parameter can have value: %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 && firstnode(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 +2667,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 +2676,8 @@ 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 && !asg->value.array &&
+		!(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)))
 		oldval = ztrdup(getsparam(asg0.name));
 	    on |= (pm->node.flags & PM_EXPORTED);
 	}
@@ -2554,12 +2685,18 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	 * Create the tied array; this is normal except that
 	 * it has the PM_TIED flag set.  Do it first because
 	 * we need the address.
+	 *
+	 * Don't attempt to set it yet, it's too early
+	 * to be exported properly.
 	 */
+	asg2.name = asg->name;
+	asg2.is_array = 0;
+	asg2.value.array = (LinkList)0;
 	if (!(apm=typeset_single(name, asg->name,
 				 (Param)paramtab->getnode(paramtab,
 							  asg->name),
 				 func, (on | PM_ARRAY) & ~PM_EXPORTED,
-				 off, roff, asg->value, NULL, ops, 0))) {
+				 off, roff, &asg2, NULL, ops, 0))) {
 	    if (oldval)
 		zsfree(oldval);
 	    unqueue_signals();
@@ -2572,7 +2709,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);
@@ -2591,7 +2728,9 @@ bin_typeset(char *name, char **argv, Options ops, int func)
 	if (apm->ename)
 	    zsfree(apm->ename);
 	apm->ename = ztrdup(asg0.name);
-	if (oldval)
+	if (asg->value.array)
+	    setaparam(asg->name, zlinklist2array(asg->value.array));
+	else if (oldval)
 	    setsparam(asg0.name, oldval);
 	unqueue_signals();
 
@@ -2611,18 +2750,18 @@ 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;
 
 	    tokenize(asg->name);   /* expand argument */
 	    if (!(pprog = patcompile(asg->name, 0, NULL))) {
 		untokenize(asg->name);
-		zwarnnam(name, "bad pattern : %s", argv[-1]);
+		zwarnnam(name, "bad pattern : %s", asg->name);
 		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 +2787,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 +2796,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 +2810,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 +3653,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 +3668,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 +3689,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 +3900,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..57e8f6376 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,20 +2537,32 @@ 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);
 	    if (!checked)
 		break;
-	    if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
-		(hn = shfunctab->getnode(shfunctab, cmdarg))) {
-		is_shfunc = 1;
-		break;
-	    }
-	    if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
-		checked = !(cflags & BINF_BUILTIN);
-		break;
+	    if (type == WC_TYPESET &&
+		(hn = builtintab->getnode2(builtintab, cmdarg))) {
+		/*
+		 * If reserved word for typeset command found (and so
+		 * enabled), use regardless of whether builtin is
+		 * enabled as we share the implementation.
+		 *
+		 * Reserved words take precedence over shell functions.
+		 */
+		checked = 1;
+	    } else {
+		if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
+		    (hn = shfunctab->getnode(shfunctab, cmdarg))) {
+		    is_shfunc = 1;
+		    break;
+		}
+		if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
+		    checked = !(cflags & BINF_BUILTIN);
+		    break;
+		}
 	    }
 	    orig_cflags |= cflags;
 	    cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
@@ -2661,7 +2693,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 +2929,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 +3489,117 @@ 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--) {
+			wordcode ac = *state->pc++;
+			char *name = ecgetstr(state, EC_DUPTOK, &htok);
+			Asgment asg;
+			local_list1(svl);
+
+			DPUTS(wc_code(ac) != WC_ASSIGN,
+			      "BUG: bad assignment list for typeset");
+			if (htok) {
+			    init_list1(svl, name);
+			    if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR &&
+				WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
+				char *data;
+				/*
+				 * Special case: this is a name only, so
+				 * it's not required to be a single
+				 * expansion.  Furthermore, for
+				 * consistency with the builtin
+				 * interface, it may expand into
+				 * scalar assignments:
+				 *  ass=(one=two three=four)
+				 *  typeset a=b $ass
+				 */
+				/* Unused dummy value for name */
+				(void)ecgetstr(state, EC_DUPTOK, &htok);
+				prefork(&svl, PREFORK_TYPESET);
+				if (errflag) {
+				    state->pc = opc;
+				    break;
+				}
+				globlist(&svl, 0);
+				if (errflag) {
+				    state->pc = opc;
+				    break;
+				}
+				while ((data = ugetnode(&svl))) {
+				    char *ptr;
+				    asg = (Asgment)zhalloc(sizeof(struct asgment));
+				    asg->is_array = 0;
+				    if ((ptr = strchr(data, '='))) {
+					*ptr++ = '\0';
+					asg->name = data;
+					asg->value.scalar = ptr;
+				    } else {
+					asg->name = data;
+					asg->value.scalar = NULL;
+				    }
+				    uaddlinknode(assigns, &asg->node);
+				}
+				continue;
+			    }
+			    prefork(&svl, PREFORK_SINGLE);
+			    name = empty(&svl) ? "" :
+				(char *)getdata(firstnode(&svl));
+			}
+			untokenize(name);
+			asg = (Asgment)zhalloc(sizeof(struct asgment));
+			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 +3641,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..90739a882 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -1050,22 +1050,29 @@ 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},
     {{NULL, "else", 0}, ELSE},
     {{NULL, "end", 0}, ZEND},
     {{NULL, "esac", 0}, ESAC},
+    {{NULL, "export", 0}, TYPESET},
     {{NULL, "fi", 0}, FI},
+    {{NULL, "float", 0}, TYPESET},
     {{NULL, "for", 0}, FOR},
     {{NULL, "foreach", 0}, FOREACH},
     {{NULL, "function", 0}, FUNC},
     {{NULL, "if", 0}, IF},
+    {{NULL, "integer", 0}, TYPESET},
+    {{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/options.c b/Src/options.c
index 78f603de1..da3d8308a 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -172,7 +172,7 @@ static struct optname optns[] = {
 {{NULL, "kshautoload",	      OPT_EMULATE|OPT_BOURNE},	 KSHAUTOLOAD},
 {{NULL, "kshglob",	      OPT_EMULATE|OPT_KSH},	 KSHGLOB},
 {{NULL, "kshoptionprint",     OPT_EMULATE|OPT_KSH},	 KSHOPTIONPRINT},
-{{NULL, "kshtypeset",	      OPT_EMULATE|OPT_KSH},	 KSHTYPESET},
+{{NULL, "kshtypeset",	      0},			 KSHTYPESET},
 {{NULL, "kshzerosubscript",   0},			 KSHZEROSUBSCRIPT},
 {{NULL, "listambiguous",      OPT_ALL},			 LISTAMBIGUOUS},
 {{NULL, "listbeep",	      OPT_ALL},			 LISTBEEP},
diff --git a/Src/parse.c b/Src/parse.c
index c932851d9..477f8a0ab 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,20 @@ 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 TYPESET reserved word found.
+ *     - data contains the number of string arguments (plus command)
+ *     - followed by strings
+ *     - followed by number of assignments
+ *     - followed by assignments if non-zero number.
+ *
  *   WC_SUBSH
  *     - data unused
  *     - followed by list
@@ -257,6 +271,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 +305,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 +446,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 +1008,7 @@ par_cmd(int *cmplx, int zsh_construct)
     incmdpos = 1;
     incasepat = 0;
     incond = 0;
+    intypeset = 0;
     return 1;
 }
 
@@ -1709,7 +1726,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, is_typeset = 0;
+    wordcode postassigns = 0;
 
     r = ecused;
     for (;;) {
@@ -1717,31 +1735,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 +1805,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 = is_typeset = 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,8 +1847,21 @@ par_simple(int *cmplx, int nr)
 
 	    if (!redir_var)
 	    {
-		ecstr(tokstr);
-		argc++;
+		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)) {
@@ -1833,6 +1869,50 @@ par_simple(int *cmplx, int nr)
 	    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);
+	    zshlex();
+	} else if (tok == ENVARRAY) {
+	    int n, parr;
+
+	    if (!postassigns++)
+		ppost = ecadd(0);
+
+	    parr = ecadd(0);
+	    ecstr(tokstr);
+	    cmdpush(CS_ARRAY);
+	    /*
+	     * Careful here: this must be the typeset case,
+	     * but we need to tell the lexer not to look
+	     * for assignments until we've finished the
+	     * present one.
+	     */
+	    intypeset = 0;
+	    zshlex();
+	    n = par_nl_wordlist();
+	    ecbuf[parr] = WCB_ASSIGN(WC_ASSIGN_ARRAY, WC_ASSIGN_NEW, n);
+	    cmdpop();
+	    intypeset = 1;
+	    if (tok != OUTPAR)
+		YYERROR(oecused);
+	    zshlex();
 	} else if (tok == INOUTPAR) {
 	    zlong oldlineno = lineno;
 	    int onp, so, oecssub = ecssub;
@@ -1841,7 +1921,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 +2027,18 @@ par_simple(int *cmplx, int nr)
 	return 0;
     }
     incmdpos = 1;
+    intypeset = 0;
 
-    if (!isfunc)
-	ecbuf[p] = WCB_SIMPLE(argc);
+    if (!isfunc) {
+	if (is_typeset) {
+	    ecbuf[p] = WCB_TYPESET(argc);
+	    if (postassigns)
+		ecbuf[ppost] = postassigns;
+	    else
+		ecadd(0);
+	} 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..ee0609483 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 {
@@ -789,23 +782,24 @@ struct eccstr {
 #define WC_REDIR    4
 #define WC_ASSIGN   5
 #define WC_SIMPLE   6
-#define WC_SUBSH    7
-#define WC_CURSH    8
-#define WC_TIMED    9
-#define WC_FUNCDEF 10
-#define WC_FOR     11
-#define WC_SELECT  12
-#define WC_WHILE   13
-#define WC_REPEAT  14
-#define WC_CASE    15
-#define WC_IF      16
-#define WC_COND    17
-#define WC_ARITH   18
-#define WC_AUTOFN  19
-#define WC_TRY     20
+#define WC_TYPESET  7
+#define WC_SUBSH    8
+#define WC_CURSH    9
+#define WC_TIMED   10
+#define WC_FUNCDEF 11
+#define WC_FOR     12
+#define WC_SELECT  13
+#define WC_WHILE   14
+#define WC_REPEAT  15
+#define WC_CASE    16
+#define WC_IF      17
+#define WC_COND    18
+#define WC_ARITH   19
+#define WC_AUTOFN  20
+#define WC_TRY     21
 
 /* increment as necessary */
-#define WC_COUNT   21
+#define WC_COUNT   22
 
 #define WCB_END()           wc_bld(WC_END, 0)
 
@@ -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;
diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst
index 57a7caa12..4afb18962 100644
--- a/Test/B02typeset.ztst
+++ b/Test/B02typeset.ztst
@@ -22,6 +22,8 @@
 
 %prep
 
+  mkdir typeset.tmp && cd typeset.tmp
+
   setopt noglob
 
   scalar=scalar
@@ -231,7 +233,7 @@
 
  typeset -T THIS will not work
 1:Tied array syntax
-?(eval):typeset:1: -T requires names of scalar and array
+?(eval):typeset:1: too many arguments for -T
 
  local array[2]=x
 1:Illegal local array element assignment
@@ -508,3 +510,144 @@
 >a2=(three four)
 >typeset -r r1=yes
 >typeset -r r2=no
+
+  one=hidden two=hidden three=hidden four=hidden five=hidden
+  fn() {
+     local bleugh="four=vier"
+     typeset -R10 one=eins two=(zwei dio) three $bleugh five=(cinq cinque)
+     three=drei
+     print -l $one $two $three $four $five
+  }
+  fn
+  print -l $one $two $three $four $five
+0:typeset reserved word interface: basic
+>      eins
+>zwei
+>dio
+>      drei
+>      vier
+>cinq
+>cinque
+>hidden
+>hidden
+>hidden
+>hidden
+>hidden
+
+  (
+  setopt glob
+  mkdir -p arrayglob
+  touch arrayglob/{one,two,three,four,five,six,seven}
+  fn() {
+    typeset array=(arrayglob/[tf]*)
+    print -l ${array:t}
+    #
+    typeset {first,second,third}=the_same_value array=(
+    extends
+    over
+    multiple
+    lines
+    )
+    print -l $first $second $third "$array"
+    #
+    integer i=$(echo 1 + 2 + 3 + 4)
+    print $i
+    #
+    # only noted by accident this was broken..
+    # we need to turn off special recognition
+    # of assignments within assignments...
+    typeset careful=( i=1 j=2 k=3 )
+    print -l $careful
+  }
+  fn
+  )
+0:typeset reserved word, more complicated cases
+>five
+>four
+>three
+>two
+>the_same_value
+>the_same_value
+>the_same_value
+>extends over multiple lines
+>10
+>i=1
+>j=2
+>k=3
+
+  (
+     # reserved word is recognised at parsing.
+     # yes, this is documented.
+     # anyway, that means we need to
+     # re-eval the function...
+     fn='
+     fn() {
+        typeset foo=`echo one word=two`
+        print $foo
+        print $word
+     }
+     '
+     print reserved
+     eval $fn; fn
+     print builtin
+     disable -r typeset
+     eval $fn; fn
+     enable -r typeset
+     disable typeset
+     print reserved
+     eval $fn; fn
+  )
+0:reserved word and builtin interfaces
+>reserved
+>one word=two
+>
+>builtin
+>one
+>two
+>reserved
+>one word=two
+>
+
+  fn() {
+    emulate -L zsh
+    setopt typeset_silent
+    local k
+    typeset -A hash=(k1 v1 k2 v2)
+    typeset foo=word array=(more than one word)
+    for k in ${(ko)hash}; do
+      print $k $hash[$k]
+    done
+    print -l $foo $array
+    typeset -A hash
+    typeset foo array
+    for k in ${(ko)hash}; do
+      print $k $hash[$k]
+    done
+    print -l $foo $array
+    typeset hash=(k3 v3 k4 v4) array=(odd number here)
+    for k in ${(ko)hash}; do
+      print $k $hash[$k]
+    done
+    print -l $array
+  }
+  fn
+0:typeset preserves existing variable types
+>k1 v1
+>k2 v2
+>word
+>more
+>than
+>one
+>word
+>k1 v1
+>k2 v2
+>word
+>more
+>than
+>one
+>word
+>k3 v3
+>k4 v4
+>odd
+>number
+>here
diff --git a/Test/D01prompt.ztst b/Test/D01prompt.ztst
index 3074efe60..2638e2438 100644
--- a/Test/D01prompt.ztst
+++ b/Test/D01prompt.ztst
@@ -199,5 +199,5 @@
 ?+zsh_directory_name:4> [[ d == n ]]
 ?+zsh_directory_name:12> [[ <parent>/very_long_directory_name == (#b)(*)/very_long_directory_name ]]
 ?+zsh_directory_name:14> return 0
-?+fn:7> local 'd=~[<parent>:l]'
+?+fn:7> local d='~[<parent>:l]'
 ?+fn:8> print '~[<parent>:l]'
diff --git a/Test/E01options.ztst b/Test/E01options.ztst
index d64f7ac68..ca3f06ca8 100644
--- a/Test/E01options.ztst
+++ b/Test/E01options.ztst
@@ -570,6 +570,15 @@
 >unset
 >globassign
 
+  # This test is now somewhat artificial as
+  # KSH_TYPESET only applies to the builtin
+  # interface.  Tests to the more standard
+  # reserved word interface appear elsewhere.
+  (
+  # reserved words are handled during parsing,
+  # hence eval...
+  disable -r typeset
+  eval '
   setopt kshtypeset
   ktvars=(ktv1 ktv2)
   typeset ktfoo=`echo arg1 arg2` $ktvars
@@ -580,6 +589,8 @@
   print $noktfoo
   print $+noktarg1 $+noktarg2
   unset ktfoo ktv1 ktv2 noktfoo noktarg2
+  '
+  )
 0:KSH_TYPESET option
 >1 1 0
 >arg1 arg2