summary refs log tree commit diff
path: root/Src/builtin.c
diff options
context:
space:
mode:
Diffstat (limited to 'Src/builtin.c')
-rw-r--r--Src/builtin.c309
1 files changed, 224 insertions, 85 deletions
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 ||