zsh-workers
 help / color / mirror / code / Atom feed
* PATCH: [key]=value syntax, work in progress
@ 2017-09-11 20:51 Peter Stephenson
  2017-09-11 20:56 ` Peter Stephenson
  2017-09-12 20:25 ` Peter Stephenson
  0 siblings, 2 replies; 15+ messages in thread
From: Peter Stephenson @ 2017-09-11 20:51 UTC (permalink / raw)
  To: Zsh hackers list

Very early days in an attempt to support the [key]=value syntax in array
assignment.  See tests at end for what I have done so far.

typeset does not yet support this syntax.

Note that the index does not determine the type of the array / hash
being created --- this is how bash works, and it would be a guess at
best as we allow math evaluation for numeric subscripts.

Please let me know now of anything that is going in the wrong direction
or based on a misunderstanding or just plain wrong.  The clumsiest bit
is the addition to do key / value assignment for normal arrays, but I
don't think it's actually broken.

Note globbing is suppressed for this form of array assignment.  That
seems pretty much inevitable --- turning a value into multiple values
isn't useful here.

Feel free to come up with corner cases, but I'm likely to test for
them as errors rather than add gratuitous extra support.

pws

diff --git a/Src/exec.c b/Src/exec.c
index e2432fd..73506a1 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2394,7 +2394,7 @@ static void
 addvars(Estate state, Wordcode pc, int addflags)
 {
     LinkList vl;
-    int xtr, isstr, htok = 0;
+    int xtr, isstr, htok = 0, dontglob = 0;
     char **arr, **ptr, *name;
     int flags;
 
@@ -2428,8 +2428,38 @@ addvars(Estate state, Wordcode pc, int addflags)
 	if ((isstr = (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR))) {
 	    init_list1(svl, ecgetstr(state, EC_DUPTOK, &htok));
 	    vl = &svl;
-	} else
+	} else {
+	    char *start, *end;
+	    LinkNode ve, next;
 	    vl = ecgetlist(state, WC_ASSIGN_NUM(ac), EC_DUPTOK, &htok);
+	    if (vl &&
+		(ve = firstnode(vl)) &&
+		(start = (char *)getdata(ve)) &&
+		start[0] == Inbrack &&
+		(end = strchr(start+1, Outbrack)) &&
+		end[1] == Equals) {
+		dontglob = 1;
+		myflags |= ASSPM_KEY_VALUE;
+		for (;;) {
+		    *end = '\0';
+		    next = nextnode(ve);
+		    setdata(ve, start+1);
+		    insertlinknode(vl, ve, end+2);
+		    ve = next;
+		    if (!ve)
+			break;
+		    if (!(start = (char *)getdata(ve)) ||
+			start[0] != Inbrack ||
+			!(end = strchr(start+1, Outbrack)) ||
+			end[1] != Equals) {
+			zerr("bad array element, expected [key]=value: %s",
+			     start);
+			state->pc = opc;
+			return;
+		    }
+		}
+	    }
+	}
 
 	if (vl && htok) {
 	    prefork(vl, (isstr ? (PREFORK_SINGLE|PREFORK_ASSIGN) :
@@ -2438,8 +2468,9 @@ addvars(Estate state, Wordcode pc, int addflags)
 		state->pc = opc;
 		return;
 	    }
-	    if (!isstr || (isset(GLOBASSIGN) && isstr &&
-			   haswilds((char *)getdata(firstnode(vl))))) {
+	    if (!dontglob &&
+		(!isstr || (isset(GLOBASSIGN) && isstr &&
+			   haswilds((char *)getdata(firstnode(vl)))))) {
 		globlist(vl, 0);
 		/* Unset the parameter to force it to be recreated
 		 * as either scalar or array depending on how many
diff --git a/Src/params.c b/Src/params.c
index 6fbee88..8201fa5 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -3185,6 +3185,64 @@ assignaparam(char *s, char **val, int flags)
 
     if (flags & ASSPM_WARN)
 	check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars);
+
+    if ((flags & ASSPM_KEY_VALUE) && (PM_TYPE(v->pm->node.flags) & PM_ARRAY)) {
+	int maxlen, origlen;
+	char **aptr, **fullval, *dummy;
+	zlong *subscripts = (zlong *)zhalloc(arrlen(val) * sizeof(zlong));
+	zlong *iptr = subscripts;
+	if (flags & ASSPM_AUGMENT) {
+	    maxlen = origlen = arrlen(v->pm->gsu.a->getfn(v->pm));
+	} else {
+	    maxlen = origlen = 0;
+	}
+	for (aptr = val; *aptr && aptr[1]; aptr += 2) {
+	    *iptr = mathevalarg(*aptr, &dummy);
+	    if (*iptr < 0 ||
+		(!isset(KSHARRAYS) && *iptr == 0)) {
+		zerr("bad subscript for direct array assignment: %s", *aptr);
+		return NULL;
+	    }
+	    if (!isset(KSHARRAYS))
+		--*iptr;
+	    if (*iptr + 1 > maxlen)
+		maxlen = *iptr + 1;
+	    ++iptr;
+	}
+	fullval = zshcalloc((maxlen+1) * sizeof(char *));
+	fullval[maxlen] = NULL;
+	if (flags & ASSPM_AUGMENT) {
+	    char **srcptr = v->pm->gsu.a->getfn(v->pm);
+	    for (aptr = fullval; aptr <= fullval + maxlen; aptr++) {
+		*aptr = ztrdup(*srcptr); 
+		srcptr++;
+	    }
+	}
+	iptr = subscripts;
+	for (aptr = val; *aptr && aptr[1]; aptr += 2) {
+	    zsfree(*aptr);
+	    fullval[*iptr] = aptr[1];
+	    ++iptr;
+	}
+	if (*aptr) {		/* Shouldn't be possible */
+	    DPUTS(1, "Extra element in key / value array");
+	    zsfree(*aptr);
+	}
+	free(val);
+	for (aptr = fullval; aptr < fullval + maxlen; aptr++) {
+	    /*
+	     * Remember we don't have sparse arrays but and they're null
+	     * terminated --- so any value we don't set has to be an
+	     * empty string.
+	     */
+	    if (!*aptr)
+		*aptr = ztrdup("");
+	}
+	setarrvalue(v, fullval);
+	unqueue_signals();
+	return v->pm;
+    }
+
     if (flags & ASSPM_AUGMENT) {
     	if (v->start == 0 && v->end == -1) {
 	    if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
diff --git a/Src/zsh.h b/Src/zsh.h
index 1e982a6..13fb1e1 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2060,6 +2060,11 @@ enum {
     ASSPM_WARN = (ASSPM_WARN_CREATE|ASSPM_WARN_NESTED),
     /* Import from environment, so exercise care evaluating value */
     ASSPM_ENV_IMPORT = 1 << 3,
+    /* Array is key / value pairs.
+     * This is normal for associative arrays but variant behaviour for
+     * normal arrays.
+     */
+    ASSPM_KEY_VALUE = 1 << 4
 };
 
 /* node for named directory hash table (nameddirtab) */
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index 8dbc1e8..fd098d5 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -2207,3 +2207,25 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888
 0:(z) splitting with remaining tokens
 >foo-bar*thingy?
  
+ typeset -A keyvalhash
+ keyvalhash=([one]=eins [two]=zwei)
+ keyvalhash+=([three]=drei)
+ for key in ${(ok)keyvalhash}; do
+   print $key $keyvalhash[$key]
+ done
+0:[key]=val for hashes
+>one eins
+>three drei
+>two zwei
+
+  keyvalarray=([1]=one [3]=three)
+  print -l "${keyvalarray[@]}"
+  keyvalarray+=([2]=two)
+  print -l "${keyvalarray[@]}"
+0:[key]=val for normal arrays
+>one
+>
+>three
+>one
+>two
+>three


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-11 20:51 PATCH: [key]=value syntax, work in progress Peter Stephenson
@ 2017-09-11 20:56 ` Peter Stephenson
  2017-09-12 20:25 ` Peter Stephenson
  1 sibling, 0 replies; 15+ messages in thread
From: Peter Stephenson @ 2017-09-11 20:56 UTC (permalink / raw)
  To: Zsh hackers list

On Mon, 11 Sep 2017 21:51:15 +0100
Peter Stephenson <p.w.stephenson@ntlworld.com> wrote:
> Please let me know now of anything that is going in the wrong direction
> or based on a misunderstanding or just plain wrong.

Just spotted one thing already --- that prefork() in addvars() isn't
usefully placed for this.  I think the answer is probably singsub() on
keys and values separately?

pws


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-11 20:51 PATCH: [key]=value syntax, work in progress Peter Stephenson
  2017-09-11 20:56 ` Peter Stephenson
@ 2017-09-12 20:25 ` Peter Stephenson
  2017-09-12 20:28   ` Peter Stephenson
                     ` (2 more replies)
  1 sibling, 3 replies; 15+ messages in thread
From: Peter Stephenson @ 2017-09-12 20:25 UTC (permalink / raw)
  To: Zsh hackers list

On Mon, 11 Sep 2017 21:51:15 +0100
Peter Stephenson <p.w.stephenson@ntlworld.com> wrote:
> Very early days in an attempt to support the [key]=value syntax in array
> assignment.
> 
> Please let me know now of anything that is going in the wrong direction
> or based on a misunderstanding or just plain wrong.

(I take silence to mean "grmf why are you even bothering to do this
frmkfplp", with consontantal clusters different by local factors.)

Seems to be basically done.  Was expecting more of a challenge...

With a bit of luck (which we don't usually get), this shouldn't interact
too badly with anything there already, so I ought to be able to commit
this before I go on holiday at the weekend.

Please at least read the initial description paragraph to see if you
think it needs to work differently.

pws

diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 817496b..5a6c85b 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -91,13 +91,32 @@ cindex(array assignment)
 ifzman()
 indent(tt(set -A) var(name) var(value) ...)
 indent(var(name)tt(=LPAR())var(value) ...tt(RPAR()))
+indent(var(name)tt(=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
+
+In the third form, var(key) is an expression that will be evaluated in
+arithmetic context (in its simplest form, an integer) that gives the
+index of the element to be assigned with var(value).  In this form any
+elements not explicitly mentioned that come before the largest index to
+which a value is assigned will be assigned an empty string. The indices
+may be in any order.  Note that this syntax is strict: tt([) and tt(]=) must
+not be quoted, while var(key) may not consist of the unquoted string
+tt(]=), but is otherwise treated as a simple string.  Furthermore, all
+elements must match this form or an error is genereted; likewise, if the
+first entry does not match this form any later entry that does is taken
+as a simple value rather than a key / value pair. The enhanced forms of
+subscript expression that may be used when directly subscripting a
+variable name, described in the section Array Subscripts below, are not
+available.  Both var(key) and var(value) undergo all forms of expansion
+allowed for single word substitutions (this does not include filename
+generation).
 
 If no parameter var(name) exists, an ordinary array parameter is created.
 If the parameter var(name) exists and is a scalar, it is replaced by a new
 array.  To append to an array without changing the existing values, use
-the syntax:
+one of the following:
 ifzman()
 indent(var(name)tt(+=LPAR())var(value) ...tt(RPAR()))
+indent(var(name)tt(+=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
 
 Within the parentheses on the right hand side of either form of the
 assignment, newlines and semicolons are treated the same as white space,
@@ -118,12 +137,14 @@ is interpreted as alternating keys and values:
 ifzman()
 indent(tt(set -A) var(name) var(key) var(value) ...)
 indent(var(name)tt(=LPAR())var(key) var(value) ...tt(RPAR()))
+indent(var(name)tt(=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
 
 Every var(key) must have a var(value) in this case.  Note that this
 assigns to the entire array, deleting any elements that do not appear in
 the list.  The append syntax may also be used with an associative array:
 ifzman()
 indent(var(name)tt(+=LPAR())var(key) var(value) ...tt(RPAR()))
+indent(var(name)tt(+=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
 
 This adds a new key/value pair if the key is not already present, and
 replaces the value for the existing key if it is.
diff --git a/Src/builtin.c b/Src/builtin.c
index 0c2a62a..f5ccf52 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -450,15 +450,35 @@ execbuiltin(LinkList args, LinkList assigns, Builtin bn)
 		    Asgment asg = (Asgment)node;
 		    fputc(' ', xtrerr);
 		    quotedzputs(asg->name, xtrerr);
-		    if (asg->is_array) {
-			LinkNode arrnode;
+		    if (asg->flags & ASG_ARRAY) {
 			fprintf(xtrerr, "=(");
 			if (asg->value.array) {
-			    for (arrnode = firstnode(asg->value.array);
-				 arrnode;
-				 incnode(arrnode)) {
-				fputc(' ', xtrerr);
-				quotedzputs((char *)getdata(arrnode), xtrerr);
+			    if (asg->flags & ASG_KEY_VALUE) {
+				LinkNode keynode, valnode;
+				keynode = firstnode(asg->value.array);
+				for (;;) {
+				    if (!keynode)
+					break;
+				    valnode = nextnode(keynode);
+				    if (!valnode)
+					break;
+				    fputc('[', xtrerr);
+				    quotedzputs((char *)getdata(keynode),
+						xtrerr);
+				    fprintf(stderr, "]=");
+				    quotedzputs((char *)getdata(valnode),
+						xtrerr);
+				    keynode = nextnode(valnode);
+				}
+			    } else {
+				LinkNode arrnode;
+				for (arrnode = firstnode(asg->value.array);
+				     arrnode;
+				     incnode(arrnode)) {
+				    fputc(' ', xtrerr);
+				    quotedzputs((char *)getdata(arrnode),
+						xtrerr);
+				}
 			    }
 			}
 			fprintf(xtrerr, " )");
@@ -1519,7 +1539,7 @@ bin_fc(char *nam, char **argv, Options ops, int func)
 	    asgl = a;
 	}
 	a->name = *argv;
-	a->is_array = 0;
+	a->flags = 0;
 	a->value.scalar = s;
 	a->node.next = a->node.prev = NULL;
 	argv++;
@@ -1910,7 +1930,7 @@ getasg(char ***argvp, LinkList assigns)
 	return NULL;
     }
     asg.name = s;
-    asg.is_array = 0;
+    asg.flags = 0;
 
     /* search for `=' */
     for (; *s && *s != '='; s++);
@@ -2171,7 +2191,7 @@ 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 ?
+	if ((asg->flags & ASG_ARRAY) ?
 	    !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) :
 	    (asg->value.scalar && (PM_TYPE(pm->node.flags &
 					   (PM_ARRAY|PM_HASHED))))) {
@@ -2241,10 +2261,11 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	    if (asg->value.scalar &&
 		!(pm = assignsparam(pname, ztrdup(asg->value.scalar), 0)))
 		return NULL;
-	} else if (asg->is_array) {
+	} else if (asg->flags & ASG_ARRAY) {
+	    int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
 	    if (!(pm = assignaparam(pname, asg->value.array ?
 				 zlinklist2array(asg->value.array) :
-				 mkarray(NULL), 0)))
+				 mkarray(NULL), flags)))
 		return NULL;
 	}
 	if (errflag)
@@ -2255,7 +2276,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	return pm;
     }
 
-    if (asg->is_array ?
+    if ((asg->flags & ASG_ARRAY) ?
 	!(on & (PM_ARRAY|PM_HASHED)) :
 	(asg->value.scalar && (on & (PM_ARRAY|PM_HASHED)))) {
 	zerrnam(cname, "%s: inconsistent type for assignment", pname);
@@ -2287,7 +2308,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	 */
 	if (!ASG_VALUEP(asg) && !((pm->node.flags|on) & (PM_ARRAY|PM_HASHED))) {
 	    asg->value.scalar = dupstring(getsparam(pname));
-	    asg->is_array = 0;
+	    asg->flags = 0;
 	}
 	/* pname may point to pm->nam which is about to disappear */
 	pname = dupstring(pname);
@@ -2396,13 +2417,14 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 		      ztrdup(asg->value.scalar ? asg->value.scalar : ""), 0)))
 		return NULL;
 	    dont_set = 1;
-	    asg->is_array = 0;
+	    asg->flags = 0;
 	    keeplocal = 0;
 	    on = pm->node.flags;
 	} else if (PM_TYPE(on) == PM_ARRAY && ASG_ARRAYP(asg)) {
+	    int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
 	    if (!(pm = assignaparam(pname, asg->value.array ?
 				    zlinklist2array(asg->value.array) :
-				    mkarray(NULL), 0)))
+				    mkarray(NULL), flags)))
 		return NULL;
 	    dont_set = 1;
 	    keeplocal = 0;
@@ -2479,6 +2501,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	Param ipm = pm;
 	if (pm->node.flags & (PM_ARRAY|PM_HASHED)) {
 	    char **arrayval;
+	    int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
 	    if (!ASG_ARRAYP(asg)) {
 		/*
 		 * Attempt to assign a scalar value to an array.
@@ -2497,7 +2520,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 		arrayval = zlinklist2array(asg->value.array);
 	    else
 		arrayval = mkarray(NULL);
-	    if (!(pm=assignaparam(pname, arrayval, 0)))
+	    if (!(pm=assignaparam(pname, arrayval, flags)))
 		return NULL;
 	} else {
 	    DPUTS(ASG_ARRAYP(asg), "BUG: inconsistent array value for scalar");
@@ -2750,13 +2773,15 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 		     * Already tied in the fashion requested.
 		     */
 		    struct tieddata *tdp = (struct tieddata*)pm->u.data;
+		    int flags = (asg->flags & ASG_KEY_VALUE) ?
+			ASSPM_KEY_VALUE : 0;
 		    /* Update join character */
 		    tdp->joinchar = joinchar;
 		    if (asg0.value.scalar)
 			assignsparam(asg0.name, ztrdup(asg0.value.scalar), 0);
 		    else if (asg->value.array)
 			assignaparam(
-			    asg->name, zlinklist2array(asg->value.array), 0);
+			    asg->name, zlinklist2array(asg->value.array),flags);
 		    return 0;
 		} else {
 		    zwarnnam(name, "can't tie already tied scalar: %s",
@@ -2778,7 +2803,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 	 * to be exported properly.
 	 */
 	asg2.name = asg->name;
-	asg2.is_array = 0;
+	asg2.flags = 0;
 	asg2.value.array = (LinkList)0;
 	if (!(apm=typeset_single(name, asg->name,
 				 (Param)paramtab->getnode(paramtab,
@@ -2816,9 +2841,10 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 	if (apm->ename)
 	    zsfree(apm->ename);
 	apm->ename = ztrdup(asg0.name);
-	if (asg->value.array)
-	    assignaparam(asg->name, zlinklist2array(asg->value.array), 0);
-	else if (oldval)
+	if (asg->value.array) {
+	    int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
+	    assignaparam(asg->name, zlinklist2array(asg->value.array), flags);
+	} else if (oldval)
 	    assignsparam(asg0.name, oldval, 0);
 	unqueue_signals();
 
diff --git a/Src/exec.c b/Src/exec.c
index e2432fd..d136766 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2389,6 +2389,60 @@ addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag,
     }
 }
 
+/* Check for array assignent with entries like [key]=val.
+ *
+ * All entries or none must match this form, else error and return 0.
+ *
+ * Convert list to alternate key / val form, perform
+ * appropriate substitution, and return 1 if found.
+ *
+ * Caller to check errflag.
+ */
+
+/**/
+static int
+keyvalpairarray(LinkList vl, int htok)
+{
+    char *start, *end, *dat;
+    LinkNode ve, next;
+
+    if (vl &&
+	(ve = firstnode(vl)) &&
+	(start = (char *)getdata(ve)) &&
+	start[0] == Inbrack &&
+	(end = strchr(start+1, Outbrack)) &&
+	end[1] == Equals) {
+	for (;;) {
+	    *end = '\0';
+	    next = nextnode(ve);
+
+	    dat = start + 1;
+	    if (htok)
+		singsub(&dat);
+	    untokenize(dat);
+	    setdata(ve, dat);
+	    dat = end + 2;
+	    if (htok)
+		singsub(&dat);
+	    untokenize(dat);
+	    insertlinknode(vl, ve, dat);
+	    ve = next;
+	    if (!ve)
+		break;
+	    if (!(start = (char *)getdata(ve)) ||
+		start[0] != Inbrack ||
+		!(end = strchr(start+1, Outbrack)) ||
+		end[1] != Equals) {
+		zerr("bad array element, expected [key]=value: %s",
+		     start);
+		return 0;
+	    }
+	}
+	return 1;
+    }
+    return 0;
+}
+
 /**/
 static void
 addvars(Estate state, Wordcode pc, int addflags)
@@ -2428,8 +2482,17 @@ addvars(Estate state, Wordcode pc, int addflags)
 	if ((isstr = (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR))) {
 	    init_list1(svl, ecgetstr(state, EC_DUPTOK, &htok));
 	    vl = &svl;
-	} else
+	} else {
 	    vl = ecgetlist(state, WC_ASSIGN_NUM(ac), EC_DUPTOK, &htok);
+	    if (keyvalpairarray(vl, htok)) {
+		myflags |= ASSPM_KEY_VALUE;
+		htok = 0;
+	    }
+	    if (errflag) {
+		state->pc = opc;
+		return;
+	    }
+	}
 
 	if (vl && htok) {
 	    prefork(vl, (isstr ? (PREFORK_SINGLE|PREFORK_ASSIGN) :
@@ -3914,7 +3977,7 @@ execcmd_exec(Estate state, Execcmd_params eparams,
 				while ((data = ugetnode(&svl))) {
 				    char *ptr;
 				    asg = (Asgment)zhalloc(sizeof(struct asgment));
-				    asg->is_array = 0;
+				    asg->flags = 0;
 				    if ((ptr = strchr(data, '='))) {
 					*ptr++ = '\0';
 					asg->name = data;
@@ -3936,7 +3999,7 @@ execcmd_exec(Estate state, Execcmd_params eparams,
 			asg->name = name;
 			if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR) {
 			    char *val = ecgetstr(state, EC_DUPTOK, &htok);
-			    asg->is_array = 0;
+			    asg->flags = 0;
 			    if (WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
 				/* Fake assignment, no value */
 				asg->value.scalar = NULL;
@@ -3961,18 +4024,23 @@ execcmd_exec(Estate state, Execcmd_params eparams,
 				asg->value.scalar = val;
 			    }
 			} else {
-			    asg->is_array = 1;
+			    asg->flags = ASG_ARRAY;
 			    asg->value.array =
 				ecgetlist(state, WC_ASSIGN_NUM(ac),
 					  EC_DUPTOK, &htok);
 			    if (asg->value.array)
 			    {
-				prefork(asg->value.array, PREFORK_ASSIGN, NULL);
-				if (errflag) {
-				    state->pc = opc;
-				    break;
+				if (keyvalpairarray(asg->value.array, 1))
+				    asg->flags |= ASG_KEY_VALUE;
+				else if (!errflag) {
+				    prefork(asg->value.array, PREFORK_ASSIGN,
+					    NULL);
+				    if (errflag) {
+					state->pc = opc;
+					break;
+				    }
+				    globlist(asg->value.array, 0);
 				}
-				globlist(asg->value.array, 0);
 				if (errflag) {
 				    state->pc = opc;
 				    break;
diff --git a/Src/params.c b/Src/params.c
index 6fbee88..05989a6 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -3185,6 +3185,65 @@ assignaparam(char *s, char **val, int flags)
 
     if (flags & ASSPM_WARN)
 	check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars);
+
+    if ((flags & ASSPM_KEY_VALUE) && (PM_TYPE(v->pm->node.flags) & PM_ARRAY)) {
+	int maxlen, origlen;
+	char **aptr, **fullval, *dummy;
+	zlong *subscripts = (zlong *)zhalloc(arrlen(val) * sizeof(zlong));
+	zlong *iptr = subscripts;
+	if (flags & ASSPM_AUGMENT) {
+	    maxlen = origlen = arrlen(v->pm->gsu.a->getfn(v->pm));
+	} else {
+	    maxlen = origlen = 0;
+	}
+	for (aptr = val; *aptr && aptr[1]; aptr += 2) {
+	    *iptr = mathevalarg(*aptr, &dummy);
+	    if (*iptr < 0 ||
+		(!isset(KSHARRAYS) && *iptr == 0)) {
+		unqueue_signals();
+		zerr("bad subscript for direct array assignment: %s", *aptr);
+		return NULL;
+	    }
+	    if (!isset(KSHARRAYS))
+		--*iptr;
+	    if (*iptr + 1 > maxlen)
+		maxlen = *iptr + 1;
+	    ++iptr;
+	}
+	fullval = zshcalloc((maxlen+1) * sizeof(char *));
+	fullval[maxlen] = NULL;
+	if (flags & ASSPM_AUGMENT) {
+	    char **srcptr = v->pm->gsu.a->getfn(v->pm);
+	    for (aptr = fullval; aptr <= fullval + maxlen; aptr++) {
+		*aptr = ztrdup(*srcptr); 
+		srcptr++;
+	    }
+	}
+	iptr = subscripts;
+	for (aptr = val; *aptr && aptr[1]; aptr += 2) {
+	    zsfree(*aptr);
+	    fullval[*iptr] = aptr[1];
+	    ++iptr;
+	}
+	if (*aptr) {		/* Shouldn't be possible */
+	    DPUTS(1, "Extra element in key / value array");
+	    zsfree(*aptr);
+	}
+	free(val);
+	for (aptr = fullval; aptr < fullval + maxlen; aptr++) {
+	    /*
+	     * Remember we don't have sparse arrays but and they're null
+	     * terminated --- so any value we don't set has to be an
+	     * empty string.
+	     */
+	    if (!*aptr)
+		*aptr = ztrdup("");
+	}
+	setarrvalue(v, fullval);
+	unqueue_signals();
+	return v->pm;
+    }
+
     if (flags & ASSPM_AUGMENT) {
     	if (v->start == 0 && v->end == -1) {
 	    if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
diff --git a/Src/zsh.h b/Src/zsh.h
index 1e982a6..27642f2 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1217,17 +1217,25 @@ struct alias {
 struct asgment {
     struct linknode node;
     char *name;
-    int is_array;
+    int flags;
     union {
 	char *scalar;
 	LinkList array;
     } value;
 };
 
+/* Flags for flags element of asgment */
+enum {
+    /* Array value */
+    ASG_ARRAY = 1,
+    /* Key / value array pair */
+    ASG_KEY_VALUE = 2
+};
+
 /*
  * Assignment is array?
  */
-#define ASG_ARRAYP(asg) ((asg)->is_array)
+#define ASG_ARRAYP(asg) ((asg)->flags & ASG_ARRAY)
 
 /*
  * Assignment has value?
@@ -2060,6 +2068,11 @@ enum {
     ASSPM_WARN = (ASSPM_WARN_CREATE|ASSPM_WARN_NESTED),
     /* Import from environment, so exercise care evaluating value */
     ASSPM_ENV_IMPORT = 1 << 3,
+    /* Array is key / value pairs.
+     * This is normal for associative arrays but variant behaviour for
+     * normal arrays.
+     */
+    ASSPM_KEY_VALUE = 1 << 4
 };
 
 /* node for named directory hash table (nameddirtab) */
diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst
index b27bb4f..ae21804 100644
--- a/Test/B02typeset.ztst
+++ b/Test/B02typeset.ztst
@@ -721,3 +721,58 @@
 # 'date' did not run.
 >Status is printed, 1
 *?*: failed to change user ID: *
+
+ typeset -A keyvalhash=([one]=eins [two]=zwei)
+ keyvalhash+=([three]=drei)
+ for key in ${(ok)keyvalhash}; do
+   print $key $keyvalhash[$key]
+ done
+0:[key]=val for hashes
+>one eins
+>three drei
+>two zwei
+
+  local keyvalarray=([1]=one [3]=three)
+  print -l "${keyvalarray[@]}"
+  keyvalarray+=([2]=two)
+  print -l "${keyvalarray[@]}"
+  local keyvalarray=([1]=one [3]=three)
+  print -l "${keyvalarray[@]}"
+0:[key]=val for normal arrays
+>one
+>
+>three
+>one
+>two
+>three
+>one
+>
+>three
+
+ touch foo Xnot_globbedX
+ inkey="another key" val="another value"
+ typeset -A keyvalhash=([$(echo the key)]=$(echo the value)
+                        [$inkey]=$val
+	                [*]=?not_globbed?)
+ for key in ${(ok)keyvalhash}; do
+   print -l $key $keyvalhash[$key]
+ done
+ typeset -A keyvalhash=([$(echo the key)]=$(echo the value)
+                        [$inkey]=$val
+	                [*]=?not_globbed?)
+ for key in ${(ok)keyvalhash}; do
+   print -l $key $keyvalhash[$key]
+ done
+0:Substitution in [key]=val syntax
+>*
+>?not_globbed?
+>another key
+>another value
+>the key
+>the value
+>*
+>?not_globbed?
+>another key
+>another value
+>the key
+>the value
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index 8dbc1e8..723f081 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -2207,3 +2207,43 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888
 0:(z) splitting with remaining tokens
 >foo-bar*thingy?
  
+ typeset -A keyvalhash
+ keyvalhash=([one]=eins [two]=zwei)
+ keyvalhash+=([three]=drei)
+ for key in ${(ok)keyvalhash}; do
+   print $key $keyvalhash[$key]
+ done
+0:[key]=val for hashes
+>one eins
+>three drei
+>two zwei
+
+  local keyvalarray
+  keyvalarray=([1]=one [3]=three)
+  print -l "${keyvalarray[@]}"
+  keyvalarray+=([2]=two)
+  print -l "${keyvalarray[@]}"
+0:[key]=val for normal arrays
+>one
+>
+>three
+>one
+>two
+>three
+
+ typeset -A keyvalhash
+ touch foo Xnot_globbedX
+ key="another key" val="another value"
+ keyvalhash=([$(echo the key)]=$(echo the value)
+             [$key]=$val
+	     [*]=?not_globbed?)
+ for key in ${(ok)keyvalhash}; do
+   print -l $key $keyvalhash[$key]
+ done
+0:Substitution in [key]=val syntax
+>*
+>?not_globbed?
+>another key
+>another value
+>the key
+>the value


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-12 20:25 ` Peter Stephenson
@ 2017-09-12 20:28   ` Peter Stephenson
  2017-09-13  7:13   ` Bart Schaefer
  2017-09-13  9:14   ` Oliver Kiddle
  2 siblings, 0 replies; 15+ messages in thread
From: Peter Stephenson @ 2017-09-12 20:28 UTC (permalink / raw)
  To: Zsh hackers list

On Tue, 12 Sep 2017 21:25:19 +0100
Peter Stephenson <p.w.stephenson@ntlworld.com> wrote:
> Note that this syntax is strict: tt([) and tt(]=) must
> not be quoted, while var(key) may not consist of the unquoted string
> tt(]=), but is otherwise treated as a simple string.  Furthermore, all
> elements must match this form or an error is genereted

OK, so bash says this is valid:

$ array=([3]=three four [1]=one two)
$ echo ${array[*]}
one two three four

I'll fix that another time.

pws


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-12 20:25 ` Peter Stephenson
  2017-09-12 20:28   ` Peter Stephenson
@ 2017-09-13  7:13   ` Bart Schaefer
  2017-09-13  8:53     ` Peter Stephenson
  2017-09-13  9:14   ` Oliver Kiddle
  2 siblings, 1 reply; 15+ messages in thread
From: Bart Schaefer @ 2017-09-13  7:13 UTC (permalink / raw)
  To: Zsh hackers list

On Tue, Sep 12, 2017 at 1:25 PM, Peter Stephenson
<p.w.stephenson@ntlworld.com> wrote:
>
> (I take silence to mean "grmf why are you even bothering to do this
> frmkfplp", with consontantal clusters different by local factors.)

Or just lack of time ...

>  If the parameter var(name) exists and is a scalar, it is replaced by a new
>  array.  To append to an array without changing the existing values, use
> -the syntax:
> +one of the following:
>  ifzman()
>  indent(var(name)tt(+=LPAR())var(value) ...tt(RPAR()))
> +indent(var(name)tt(+=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))

What does "append" mean when the keys are specified?  If I have

arr=( 1 2 3 4 5 )

and I do

arr+=( 6 [2]=7 8 )

does that even make sense?  That's certainly not "appending" to arr[2].

The other point that this raises is that in ksh "typeset -p" of an associative
array outputs e.g.

typeset -A x=([y]=2 [z]=1)

and that's the only way to assign an associative array; if you assign without
the [k]=v syntax the parameter converts into an ordinary array.  Is this going
to get enforced when KSH_ARRAYS and/or KSH_TYPESET are in effect?


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-13  7:13   ` Bart Schaefer
@ 2017-09-13  8:53     ` Peter Stephenson
  2017-09-13 16:44       ` Bart Schaefer
  0 siblings, 1 reply; 15+ messages in thread
From: Peter Stephenson @ 2017-09-13  8:53 UTC (permalink / raw)
  To: Zsh hackers list

On Wed, 13 Sep 2017 00:13:35 -0700
Bart Schaefer <schaefer@brasslantern.com> wrote:
> >  If the parameter var(name) exists and is a scalar, it is replaced by a new
> >  array.  To append to an array without changing the existing values, use
> > -the syntax:
> > +one of the following:
> >  ifzman()
> >  indent(var(name)tt(+=LPAR())var(value) ...tt(RPAR()))
> > +indent(var(name)tt(+=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
> 
> What does "append" mean when the keys are specified?  If I have
> 
> arr=( 1 2 3 4 5 )
> 
> and I do
> 
> arr+=( 6 [2]=7 8 )
> 
> does that even make sense?  That's certainly not "appending" to arr[2].

That's an override; it will work, but as you say describing it as
"append" isn't very helpful, so some extra words are useful.  There's
prior art for this as it's how associative arrays already behave:

% typeset -A hash=(one un two deux)
% hash+=(one eins)
% print -aC2 ${(kv)hash}
one  eins
two  deux

> The other point that this raises is that in ksh "typeset -p" of an associative
> array outputs e.g.
> 
> typeset -A x=([y]=2 [z]=1)
> 
> and that's the only way to assign an associative array; if you assign without
> the [k]=v syntax the parameter converts into an ordinary array.  Is this going
> to get enforced when KSH_ARRAYS and/or KSH_TYPESET are in effect?

That's probably a good idea.

By the way, the mixed syntax is going to be a nightmare:

$ array=(* [100]=foo)

is entirely valid, which means much more work either passing through
a list with extra structure to indicate the differences or trawling
through them to fix up one way or the other, in either case with
different expansion rules applied to different elements individually.

I suppose it's not that common, so doesn't have to be particularly
efficient as long as the traditional zsh way isn't affected, so I'm
inclined to the latter, i.e. convert to the new format if we detect it
in any element. The biggest problem there is that we don't know it's an
associative array till later so it's just going to have to be a place
marker that just gets removed in that case.  And then there are cases
like

typeset -A hash
hash=(key1 [key2]=value)

Plus I'd like to avoid an extra trawl through the entire list of
arguments just to check the format.

pws


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-12 20:25 ` Peter Stephenson
  2017-09-12 20:28   ` Peter Stephenson
  2017-09-13  7:13   ` Bart Schaefer
@ 2017-09-13  9:14   ` Oliver Kiddle
  2017-09-13 19:41     ` Peter Stephenson
  2017-09-14  6:54     ` 'typeset -p' of assocs (was: Re: PATCH: [key]=value syntax, work in progress) Daniel Shahaf
  2 siblings, 2 replies; 15+ messages in thread
From: Oliver Kiddle @ 2017-09-13  9:14 UTC (permalink / raw)
  To: Peter Stephenson; +Cc: Zsh hackers list

Peter wrote:
> (I take silence to mean "grmf why are you even bothering to do this

Not at all. Is definitely an enrichment to zsh.

> +elements must match this form or an error is genereted; likewise, if the

Note typo.

Appending has issues - seg fault with:
  arr=( 1 2 3 )
  arr+=( [5]=val )

Bash actually appends element wise with +=. Ksh doesn't. Ksh instead would
require an inner +=. Ksh behaviour on this seems far more logical to me.
  ksh$ arr+=( [0]+=X )
  bash$ arr+=( [0]=X )

A range doesn't evoke an error, (or clever multi-element assignment):
  % arr=( [1,2]=x )
  % typeset -p arr 
typeset -a arr=( x )
Note that is the first element so it must be parsing the range rather than
treating the , as the arithmentic , operator (and returning 2).

What about brace expansion:
  % arr=( [{2..3}]=x )
  zsh: bad math expression: operand expected at `{2..3}'
  bash$ arr=( [{2..3}]=x )
  bash$ typeset -p arr
  declare -a arr='([0]="[2]=x" [1]="[3]=x")'
ksh prints a syntax error.

  % arr=( [2]={a,b,c} )
  % typeset -p arr
  typeset -a arr=( '' '{a,b,c}' )
Thats the same in ksh.
Bash appears to disable the []= form as soon as it sees brace expansion.
Ksh/current zsh patch behaviour seems preferable to me.

This is interesting (syntax error in bash). Could be good to reserve this
syntax for ksh style nested assignments.
% arr=( [1]=(x y z) )
% typeset -p arr     
typeset -a arr=( '(x y z)' )

Any user deserves what they get with this but note that as a fatal error, the
shell bails out completely. Might be nice if this could be non-fatal with the
effect that the array is not assigned.

% arr=( [99999999999]=hello )
zsh: fatal error: out of memory

It would be good to make typeset -p output use this new syntax by default
for associative arrays because (without other cues like alignment) it
can be clearer which items in the list are keys and which values. Are
there certain keys that can't easily be quoted with the new syntax? If
so, typeset -p could mix the two formats once that's supported.

I checked subscript flags (along with @ and *) and it is correctly not parsing
them as subscript flags so we just get bad math expression errors - which is
good. Completion does try to complete them though. Otherwise, completion needs
adapting to work for associative array assignments. It appears to already just
work for arrays apart from relying on setopt magicequalsubst to be useful after
the equals sign. 

> OK, so bash says this is valid:
> $ array=([3]=three four [1]=one two)

Note also:
  $ array=([3]=three four [1]=one two overwrite)

Oliver

.


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-13  8:53     ` Peter Stephenson
@ 2017-09-13 16:44       ` Bart Schaefer
  2017-09-14 20:48         ` Peter Stephenson
  0 siblings, 1 reply; 15+ messages in thread
From: Bart Schaefer @ 2017-09-13 16:44 UTC (permalink / raw)
  To: Peter Stephenson; +Cc: Zsh hackers list

On Wed, Sep 13, 2017 at 1:53 AM, Peter Stephenson
<p.stephenson@samsung.com> wrote:
> On Wed, 13 Sep 2017 00:13:35 -0700
> Bart Schaefer <schaefer@brasslantern.com> wrote:
>>
>> typeset -A x=([y]=2 [z]=1)
>>
>> and that's the only way to assign an associative array; if you assign without
>> the [k]=v syntax the parameter converts into an ordinary array.  Is this going
>> to get enforced when KSH_ARRAYS and/or KSH_TYPESET are in effect?
>
> That's probably a good idea.

I would anticipate a number of problems with the function libraries
for cases where "emulate zsh" is not declared.

> By the way, the mixed syntax is going to be a nightmare:
>
> $ array=(* [100]=foo)
>
> is entirely valid, which means much more work

There's an easy way out of this:  If the first element in the parens
does not use the [key]= syntax then none of them do.  That's the only
way to make the mixed syntax relatively backward-compatible with
globbing in any case.  (How DO you assign "all file names containing
an equal sign following a leading digit" to an array in bash/ksh,
anyway?)

> typeset -A hash
> hash=(key1 [key2]=value)

Either "[key2]=value" is a value, or this converts hash from an
associative array to a plain one.  The more interesting case is

typeset -A hash=(key1 [key2]=value)

where the choices are that either "[key2]=value" is a value, or the
whole thing is a syntax error (see KSH_TYPESET remarks above).

In both variations if  "is a value" is chosen then it has to be
globbed, which will probably result in a file not found.

Then we have all the worms in Oliver's can.


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-13  9:14   ` Oliver Kiddle
@ 2017-09-13 19:41     ` Peter Stephenson
  2017-09-14  7:03       ` Daniel Shahaf
  2017-09-14 20:38       ` Peter Stephenson
  2017-09-14  6:54     ` 'typeset -p' of assocs (was: Re: PATCH: [key]=value syntax, work in progress) Daniel Shahaf
  1 sibling, 2 replies; 15+ messages in thread
From: Peter Stephenson @ 2017-09-13 19:41 UTC (permalink / raw)
  To: Zsh hackers list

On Wed, 13 Sep 2017 11:14:04 +0200
Oliver Kiddle <okiddle@yahoo.co.uk> wrote:
> Appending has issues - seg fault with:
>   arr=( 1 2 3 )
>   arr+=( [5]=val )

Thanks for the testing.

That was just a maxlen that should have been origlen.

> Bash actually appends element wise with +=. Ksh doesn't. Ksh instead would
> require an inner +=. Ksh behaviour on this seems far more logical to me.
>   ksh$ arr+=( [0]+=X )
>   bash$ arr+=( [0]=X )

Yes, I was thinking about the former, but it can wait.  If we do that it
would make a bash compatibility "do you really, really want to set
this?" option fairly straightforward.

> A range doesn't evoke an error, (or clever multi-element assignment):
>   % arr=( [1,2]=x )
>   % typeset -p arr 
> typeset -a arr=( x )
> Note that is the first element so it must be parsing the range rather than
> treating the , as the arithmentic , operator (and returning 2).

That's because I'm using mathevalarg(), which is designed for the range
case, becase I blindly copied it from elsewhere in params.c.  The
obvious fix is to use mathevali() -- in which case it becomes an
ordinary comma operator, which is logically correct.  Possibly
mathevalarg and an error on a following comma would make it more
obvious?

> What about brace expansion:
>   % arr=( [{2..3}]=x )
>   zsh: bad math expression: operand expected at `{2..3}'

Looks OK to me, actually.  It's a single word expansion on the key, so
braces aren't useful in that context, and the single word in question
indeed doesn't make sens as arithmetic, which is what you need to get an
index.  More importantly, with an associative array, that becomes an
actual string that's valid as a key, as intended.

>   bash$ arr=( [{2..3}]=x )
>   bash$ typeset -p arr
>   declare -a arr='([0]="[2]=x" [1]="[3]=x")'

This doesn't look very useful.
 
> This is interesting (syntax error in bash). Could be good to reserve this
> syntax for ksh style nested assignments.
> % arr=( [1]=(x y z) )
> % typeset -p arr     
> typeset -a arr=( '(x y z)' )

That's a consequence of (i) some time ago I made a parenthesis not
appearing in command position require a match so you could put spaces in
patterns and glob qualifiers without extra quotes (ii) single word
expansion, again.  The contents of the parentheses aren't actually
parsed specially, it just counts them all out and counts them all back.

> Any user deserves what they get with this but note that as a fatal error, the
> shell bails out completely. Might be nice if this could be non-fatal with the
> effect that the array is not assigned.
> 
> % arr=( [99999999999]=hello )
> zsh: fatal error: out of memory

This isn't really new.  It's a long-standing bugbear that there are so
many places where this can fail that it's hard to fix.  I've added a
test in the new allocation.

> It would be good to make typeset -p output use this new syntax by default
> for associative arrays because (without other cues like alignment) it
> can be clearer which items in the list are keys and which values. Are
> there certain keys that can't easily be quoted with the new syntax? If
> so, typeset -p could mix the two formats once that's supported.

Hmm... the path of least resistance is to quote just as we normally
would for an output word, which may be slight overkill but probably not
bad overall, and better guarantees compatibility.  You need to quote a
space in the key --- it's less obvious than in the value, but probably
logical enough.

Quoting is easy to get wrong, so I've added some tests where the key and
value use arbitrary quoting that should get removed (and do).

I'm still wondering about plans of attack for the mixed syntax.  I
thought about doing the recognition in prefork() when given a special
flag, removing all pattern characters immediately so that they pass
unchanged through globlist, and doing [something devilishly clever I
haven't worked out yet --- I don't want to create a new token if I can
help it, but maybe Marker will work] to mark the words to expand as
special.

I think I'll commit this and do the mixed syntax, typeset -p, and
internal += later.

diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 817496b..31266a7 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -91,13 +91,36 @@ cindex(array assignment)
 ifzman()
 indent(tt(set -A) var(name) var(value) ...)
 indent(var(name)tt(=LPAR())var(value) ...tt(RPAR()))
+indent(var(name)tt(=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
+
+In the third form, var(key) is an expression that will be evaluated in
+arithmetic context (in its simplest form, an integer) that gives the
+index of the element to be assigned with var(value).  In this form any
+elements not explicitly mentioned that come before the largest index to
+which a value is assigned will be assigned an empty string. The indices
+may be in any order.  Note that this syntax is strict: tt([) and tt(]=) must
+not be quoted, while var(key) may not consist of the unquoted string
+tt(]=), but is otherwise treated as a simple string.  Furthermore, all
+elements must match this form or an error is genereted; likewise, if the
+first entry does not match this form any later entry that does is taken
+as a simple value rather than a key / value pair. The enhanced forms of
+subscript expression that may be used when directly subscripting a
+variable name, described in the section Array Subscripts below, are not
+available.  Both var(key) and var(value) undergo all forms of expansion
+allowed for single word substitutions (this does not include filename
+generation).
 
 If no parameter var(name) exists, an ordinary array parameter is created.
 If the parameter var(name) exists and is a scalar, it is replaced by a new
 array.  To append to an array without changing the existing values, use
-the syntax:
+one of the following:
 ifzman()
 indent(var(name)tt(+=LPAR())var(value) ...tt(RPAR()))
+indent(var(name)tt(+=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
+
+In the second form var(key) may specify an existing index as well as an
+index off the end of the old array; any existing value is overwritten by
+var(value).
 
 Within the parentheses on the right hand side of either form of the
 assignment, newlines and semicolons are treated the same as white space,
@@ -118,12 +141,14 @@ is interpreted as alternating keys and values:
 ifzman()
 indent(tt(set -A) var(name) var(key) var(value) ...)
 indent(var(name)tt(=LPAR())var(key) var(value) ...tt(RPAR()))
+indent(var(name)tt(=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
 
 Every var(key) must have a var(value) in this case.  Note that this
 assigns to the entire array, deleting any elements that do not appear in
 the list.  The append syntax may also be used with an associative array:
 ifzman()
 indent(var(name)tt(+=LPAR())var(key) var(value) ...tt(RPAR()))
+indent(var(name)tt(+=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
 
 This adds a new key/value pair if the key is not already present, and
 replaces the value for the existing key if it is.
diff --git a/Src/builtin.c b/Src/builtin.c
index 0c2a62a..f5ccf52 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -450,15 +450,35 @@ execbuiltin(LinkList args, LinkList assigns, Builtin bn)
 		    Asgment asg = (Asgment)node;
 		    fputc(' ', xtrerr);
 		    quotedzputs(asg->name, xtrerr);
-		    if (asg->is_array) {
-			LinkNode arrnode;
+		    if (asg->flags & ASG_ARRAY) {
 			fprintf(xtrerr, "=(");
 			if (asg->value.array) {
-			    for (arrnode = firstnode(asg->value.array);
-				 arrnode;
-				 incnode(arrnode)) {
-				fputc(' ', xtrerr);
-				quotedzputs((char *)getdata(arrnode), xtrerr);
+			    if (asg->flags & ASG_KEY_VALUE) {
+				LinkNode keynode, valnode;
+				keynode = firstnode(asg->value.array);
+				for (;;) {
+				    if (!keynode)
+					break;
+				    valnode = nextnode(keynode);
+				    if (!valnode)
+					break;
+				    fputc('[', xtrerr);
+				    quotedzputs((char *)getdata(keynode),
+						xtrerr);
+				    fprintf(stderr, "]=");
+				    quotedzputs((char *)getdata(valnode),
+						xtrerr);
+				    keynode = nextnode(valnode);
+				}
+			    } else {
+				LinkNode arrnode;
+				for (arrnode = firstnode(asg->value.array);
+				     arrnode;
+				     incnode(arrnode)) {
+				    fputc(' ', xtrerr);
+				    quotedzputs((char *)getdata(arrnode),
+						xtrerr);
+				}
 			    }
 			}
 			fprintf(xtrerr, " )");
@@ -1519,7 +1539,7 @@ bin_fc(char *nam, char **argv, Options ops, int func)
 	    asgl = a;
 	}
 	a->name = *argv;
-	a->is_array = 0;
+	a->flags = 0;
 	a->value.scalar = s;
 	a->node.next = a->node.prev = NULL;
 	argv++;
@@ -1910,7 +1930,7 @@ getasg(char ***argvp, LinkList assigns)
 	return NULL;
     }
     asg.name = s;
-    asg.is_array = 0;
+    asg.flags = 0;
 
     /* search for `=' */
     for (; *s && *s != '='; s++);
@@ -2171,7 +2191,7 @@ 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 ?
+	if ((asg->flags & ASG_ARRAY) ?
 	    !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) :
 	    (asg->value.scalar && (PM_TYPE(pm->node.flags &
 					   (PM_ARRAY|PM_HASHED))))) {
@@ -2241,10 +2261,11 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	    if (asg->value.scalar &&
 		!(pm = assignsparam(pname, ztrdup(asg->value.scalar), 0)))
 		return NULL;
-	} else if (asg->is_array) {
+	} else if (asg->flags & ASG_ARRAY) {
+	    int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
 	    if (!(pm = assignaparam(pname, asg->value.array ?
 				 zlinklist2array(asg->value.array) :
-				 mkarray(NULL), 0)))
+				 mkarray(NULL), flags)))
 		return NULL;
 	}
 	if (errflag)
@@ -2255,7 +2276,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	return pm;
     }
 
-    if (asg->is_array ?
+    if ((asg->flags & ASG_ARRAY) ?
 	!(on & (PM_ARRAY|PM_HASHED)) :
 	(asg->value.scalar && (on & (PM_ARRAY|PM_HASHED)))) {
 	zerrnam(cname, "%s: inconsistent type for assignment", pname);
@@ -2287,7 +2308,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	 */
 	if (!ASG_VALUEP(asg) && !((pm->node.flags|on) & (PM_ARRAY|PM_HASHED))) {
 	    asg->value.scalar = dupstring(getsparam(pname));
-	    asg->is_array = 0;
+	    asg->flags = 0;
 	}
 	/* pname may point to pm->nam which is about to disappear */
 	pname = dupstring(pname);
@@ -2396,13 +2417,14 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 		      ztrdup(asg->value.scalar ? asg->value.scalar : ""), 0)))
 		return NULL;
 	    dont_set = 1;
-	    asg->is_array = 0;
+	    asg->flags = 0;
 	    keeplocal = 0;
 	    on = pm->node.flags;
 	} else if (PM_TYPE(on) == PM_ARRAY && ASG_ARRAYP(asg)) {
+	    int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
 	    if (!(pm = assignaparam(pname, asg->value.array ?
 				    zlinklist2array(asg->value.array) :
-				    mkarray(NULL), 0)))
+				    mkarray(NULL), flags)))
 		return NULL;
 	    dont_set = 1;
 	    keeplocal = 0;
@@ -2479,6 +2501,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	Param ipm = pm;
 	if (pm->node.flags & (PM_ARRAY|PM_HASHED)) {
 	    char **arrayval;
+	    int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
 	    if (!ASG_ARRAYP(asg)) {
 		/*
 		 * Attempt to assign a scalar value to an array.
@@ -2497,7 +2520,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 		arrayval = zlinklist2array(asg->value.array);
 	    else
 		arrayval = mkarray(NULL);
-	    if (!(pm=assignaparam(pname, arrayval, 0)))
+	    if (!(pm=assignaparam(pname, arrayval, flags)))
 		return NULL;
 	} else {
 	    DPUTS(ASG_ARRAYP(asg), "BUG: inconsistent array value for scalar");
@@ -2750,13 +2773,15 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 		     * Already tied in the fashion requested.
 		     */
 		    struct tieddata *tdp = (struct tieddata*)pm->u.data;
+		    int flags = (asg->flags & ASG_KEY_VALUE) ?
+			ASSPM_KEY_VALUE : 0;
 		    /* Update join character */
 		    tdp->joinchar = joinchar;
 		    if (asg0.value.scalar)
 			assignsparam(asg0.name, ztrdup(asg0.value.scalar), 0);
 		    else if (asg->value.array)
 			assignaparam(
-			    asg->name, zlinklist2array(asg->value.array), 0);
+			    asg->name, zlinklist2array(asg->value.array),flags);
 		    return 0;
 		} else {
 		    zwarnnam(name, "can't tie already tied scalar: %s",
@@ -2778,7 +2803,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 	 * to be exported properly.
 	 */
 	asg2.name = asg->name;
-	asg2.is_array = 0;
+	asg2.flags = 0;
 	asg2.value.array = (LinkList)0;
 	if (!(apm=typeset_single(name, asg->name,
 				 (Param)paramtab->getnode(paramtab,
@@ -2816,9 +2841,10 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
 	if (apm->ename)
 	    zsfree(apm->ename);
 	apm->ename = ztrdup(asg0.name);
-	if (asg->value.array)
-	    assignaparam(asg->name, zlinklist2array(asg->value.array), 0);
-	else if (oldval)
+	if (asg->value.array) {
+	    int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0;
+	    assignaparam(asg->name, zlinklist2array(asg->value.array), flags);
+	} else if (oldval)
 	    assignsparam(asg0.name, oldval, 0);
 	unqueue_signals();
 
diff --git a/Src/exec.c b/Src/exec.c
index e2432fd..d136766 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2389,6 +2389,60 @@ addfd(int forked, int *save, struct multio **mfds, int fd1, int fd2, int rflag,
     }
 }
 
+/* Check for array assignent with entries like [key]=val.
+ *
+ * All entries or none must match this form, else error and return 0.
+ *
+ * Convert list to alternate key / val form, perform
+ * appropriate substitution, and return 1 if found.
+ *
+ * Caller to check errflag.
+ */
+
+/**/
+static int
+keyvalpairarray(LinkList vl, int htok)
+{
+    char *start, *end, *dat;
+    LinkNode ve, next;
+
+    if (vl &&
+	(ve = firstnode(vl)) &&
+	(start = (char *)getdata(ve)) &&
+	start[0] == Inbrack &&
+	(end = strchr(start+1, Outbrack)) &&
+	end[1] == Equals) {
+	for (;;) {
+	    *end = '\0';
+	    next = nextnode(ve);
+
+	    dat = start + 1;
+	    if (htok)
+		singsub(&dat);
+	    untokenize(dat);
+	    setdata(ve, dat);
+	    dat = end + 2;
+	    if (htok)
+		singsub(&dat);
+	    untokenize(dat);
+	    insertlinknode(vl, ve, dat);
+	    ve = next;
+	    if (!ve)
+		break;
+	    if (!(start = (char *)getdata(ve)) ||
+		start[0] != Inbrack ||
+		!(end = strchr(start+1, Outbrack)) ||
+		end[1] != Equals) {
+		zerr("bad array element, expected [key]=value: %s",
+		     start);
+		return 0;
+	    }
+	}
+	return 1;
+    }
+    return 0;
+}
+
 /**/
 static void
 addvars(Estate state, Wordcode pc, int addflags)
@@ -2428,8 +2482,17 @@ addvars(Estate state, Wordcode pc, int addflags)
 	if ((isstr = (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR))) {
 	    init_list1(svl, ecgetstr(state, EC_DUPTOK, &htok));
 	    vl = &svl;
-	} else
+	} else {
 	    vl = ecgetlist(state, WC_ASSIGN_NUM(ac), EC_DUPTOK, &htok);
+	    if (keyvalpairarray(vl, htok)) {
+		myflags |= ASSPM_KEY_VALUE;
+		htok = 0;
+	    }
+	    if (errflag) {
+		state->pc = opc;
+		return;
+	    }
+	}
 
 	if (vl && htok) {
 	    prefork(vl, (isstr ? (PREFORK_SINGLE|PREFORK_ASSIGN) :
@@ -3914,7 +3977,7 @@ execcmd_exec(Estate state, Execcmd_params eparams,
 				while ((data = ugetnode(&svl))) {
 				    char *ptr;
 				    asg = (Asgment)zhalloc(sizeof(struct asgment));
-				    asg->is_array = 0;
+				    asg->flags = 0;
 				    if ((ptr = strchr(data, '='))) {
 					*ptr++ = '\0';
 					asg->name = data;
@@ -3936,7 +3999,7 @@ execcmd_exec(Estate state, Execcmd_params eparams,
 			asg->name = name;
 			if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR) {
 			    char *val = ecgetstr(state, EC_DUPTOK, &htok);
-			    asg->is_array = 0;
+			    asg->flags = 0;
 			    if (WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
 				/* Fake assignment, no value */
 				asg->value.scalar = NULL;
@@ -3961,18 +4024,23 @@ execcmd_exec(Estate state, Execcmd_params eparams,
 				asg->value.scalar = val;
 			    }
 			} else {
-			    asg->is_array = 1;
+			    asg->flags = ASG_ARRAY;
 			    asg->value.array =
 				ecgetlist(state, WC_ASSIGN_NUM(ac),
 					  EC_DUPTOK, &htok);
 			    if (asg->value.array)
 			    {
-				prefork(asg->value.array, PREFORK_ASSIGN, NULL);
-				if (errflag) {
-				    state->pc = opc;
-				    break;
+				if (keyvalpairarray(asg->value.array, 1))
+				    asg->flags |= ASG_KEY_VALUE;
+				else if (!errflag) {
+				    prefork(asg->value.array, PREFORK_ASSIGN,
+					    NULL);
+				    if (errflag) {
+					state->pc = opc;
+					break;
+				    }
+				    globlist(asg->value.array, 0);
 				}
-				globlist(asg->value.array, 0);
 				if (errflag) {
 				    state->pc = opc;
 				    break;
diff --git a/Src/params.c b/Src/params.c
index 6fbee88..e0aaaf6 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -3185,6 +3185,72 @@ assignaparam(char *s, char **val, int flags)
 
     if (flags & ASSPM_WARN)
 	check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars);
+
+    if ((flags & ASSPM_KEY_VALUE) && (PM_TYPE(v->pm->node.flags) & PM_ARRAY)) {
+	/*
+	 * This is an ordinary array with key / value pairs.
+	 */
+	int maxlen, origlen;
+	char **aptr, **fullval;
+	zlong *subscripts = (zlong *)zhalloc(arrlen(val) * sizeof(zlong));
+	zlong *iptr = subscripts;
+	if (flags & ASSPM_AUGMENT) {
+	    maxlen = origlen = arrlen(v->pm->gsu.a->getfn(v->pm));
+	} else {
+	    maxlen = origlen = 0;
+	}
+	for (aptr = val; *aptr && aptr[1]; aptr += 2) {
+	    *iptr = mathevali(*aptr);
+	    if (*iptr < 0 ||
+		(!isset(KSHARRAYS) && *iptr == 0)) {
+		unqueue_signals();
+		zerr("bad subscript for direct array assignment: %s", *aptr);
+		return NULL;
+	    }
+	    if (!isset(KSHARRAYS))
+		--*iptr;
+	    if (*iptr + 1 > maxlen)
+		maxlen = *iptr + 1;
+	    ++iptr;
+	}
+	fullval = zshcalloc((maxlen+1) * sizeof(char *));
+	if (!fullval) {
+	    zerr("array too large");
+	    return NULL;
+	}
+	fullval[maxlen] = NULL;
+	if (flags & ASSPM_AUGMENT) {
+	    char **srcptr = v->pm->gsu.a->getfn(v->pm);
+	    for (aptr = fullval; aptr <= fullval + origlen; aptr++) {
+		*aptr = ztrdup(*srcptr); 
+		srcptr++;
+	    }
+	}
+	iptr = subscripts;
+	for (aptr = val; *aptr && aptr[1]; aptr += 2) {
+	    zsfree(*aptr);
+	    fullval[*iptr] = aptr[1];
+	    ++iptr;
+	}
+	if (*aptr) {		/* Shouldn't be possible */
+	    DPUTS(1, "Extra element in key / value array");
+	    zsfree(*aptr);
+	}
+	free(val);
+	for (aptr = fullval; aptr < fullval + maxlen; aptr++) {
+	    /*
+	     * Remember we don't have sparse arrays but and they're null
+	     * terminated --- so any value we don't set has to be an
+	     * empty string.
+	     */
+	    if (!*aptr)
+		*aptr = ztrdup("");
+	}
+	setarrvalue(v, fullval);
+	unqueue_signals();
+	return v->pm;
+    }
+
     if (flags & ASSPM_AUGMENT) {
     	if (v->start == 0 && v->end == -1) {
 	    if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
diff --git a/Src/zsh.h b/Src/zsh.h
index 1e982a6..27642f2 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1217,17 +1217,25 @@ struct alias {
 struct asgment {
     struct linknode node;
     char *name;
-    int is_array;
+    int flags;
     union {
 	char *scalar;
 	LinkList array;
     } value;
 };
 
+/* Flags for flags element of asgment */
+enum {
+    /* Array value */
+    ASG_ARRAY = 1,
+    /* Key / value array pair */
+    ASG_KEY_VALUE = 2
+};
+
 /*
  * Assignment is array?
  */
-#define ASG_ARRAYP(asg) ((asg)->is_array)
+#define ASG_ARRAYP(asg) ((asg)->flags & ASG_ARRAY)
 
 /*
  * Assignment has value?
@@ -2060,6 +2068,11 @@ enum {
     ASSPM_WARN = (ASSPM_WARN_CREATE|ASSPM_WARN_NESTED),
     /* Import from environment, so exercise care evaluating value */
     ASSPM_ENV_IMPORT = 1 << 3,
+    /* Array is key / value pairs.
+     * This is normal for associative arrays but variant behaviour for
+     * normal arrays.
+     */
+    ASSPM_KEY_VALUE = 1 << 4
 };
 
 /* node for named directory hash table (nameddirtab) */
diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst
index b27bb4f..ae21804 100644
--- a/Test/B02typeset.ztst
+++ b/Test/B02typeset.ztst
@@ -721,3 +721,58 @@
 # 'date' did not run.
 >Status is printed, 1
 *?*: failed to change user ID: *
+
+ typeset -A keyvalhash=([one]=eins [two]=zwei)
+ keyvalhash+=([three]=drei)
+ for key in ${(ok)keyvalhash}; do
+   print $key $keyvalhash[$key]
+ done
+0:[key]=val for hashes
+>one eins
+>three drei
+>two zwei
+
+  local keyvalarray=([1]=one [3]=three)
+  print -l "${keyvalarray[@]}"
+  keyvalarray+=([2]=two)
+  print -l "${keyvalarray[@]}"
+  local keyvalarray=([1]=one [3]=three)
+  print -l "${keyvalarray[@]}"
+0:[key]=val for normal arrays
+>one
+>
+>three
+>one
+>two
+>three
+>one
+>
+>three
+
+ touch foo Xnot_globbedX
+ inkey="another key" val="another value"
+ typeset -A keyvalhash=([$(echo the key)]=$(echo the value)
+                        [$inkey]=$val
+	                [*]=?not_globbed?)
+ for key in ${(ok)keyvalhash}; do
+   print -l $key $keyvalhash[$key]
+ done
+ typeset -A keyvalhash=([$(echo the key)]=$(echo the value)
+                        [$inkey]=$val
+	                [*]=?not_globbed?)
+ for key in ${(ok)keyvalhash}; do
+   print -l $key $keyvalhash[$key]
+ done
+0:Substitution in [key]=val syntax
+>*
+>?not_globbed?
+>another key
+>another value
+>the key
+>the value
+>*
+>?not_globbed?
+>another key
+>another value
+>the key
+>the value
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index 8dbc1e8..367bca1 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -2207,3 +2207,71 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888
 0:(z) splitting with remaining tokens
 >foo-bar*thingy?
  
+ typeset -A keyvalhash
+ keyvalhash=([one]=eins [two]=zwei)
+ keyvalhash+=([three]=drei)
+ for key in ${(ok)keyvalhash}; do
+   print $key $keyvalhash[$key]
+ done
+0:[key]=val for hashes
+>one eins
+>three drei
+>two zwei
+
+  local keyvalarray
+  keyvalarray=([1]=one [3]=three)
+  print -l "${keyvalarray[@]}"
+  keyvalarray+=([2]=two)
+  print -l "${keyvalarray[@]}"
+0:[key]=val for normal arrays
+>one
+>
+>three
+>one
+>two
+>three
+
+ typeset -A keyvalhash
+ touch foo Xnot_globbedX
+ key="another key" val="another value"
+ keyvalhash=([$(echo the key)]=$(echo the value)
+             [$key]=$val
+	     [*]=?not_globbed?)
+ for key in ${(ok)keyvalhash}; do
+   print -l $key $keyvalhash[$key]
+ done
+0:Substitution in [key]=val syntax
+>*
+>?not_globbed?
+>another key
+>another value
+>the key
+>the value
+
+ local keyvalarray
+ keyvalarray=(1 2 3)
+ keyvalarray+=([5]=5 [7]=7)
+ keyvalarray+=([4]=4 [6]=6)
+ print $#keyvalarray
+ print $keyvalarray
+0:append to normal array using [key]=val
+>7
+>1 2 3 4 5 6 7
+
+ local -A keyvalhash
+ keyvalhash=(['1first element!']=first' 'value
+	     ["2second element?"]=second" "value
+	     [$'3third element#']=third$' 'value
+	     [\4\f\o\u\r\t\h\ \e\l\e\m\e\n\t\\]=fourth\ value)
+ for key in ${(ok)keyvalhash}; do
+   print -rl -- $key $keyvalhash[$key]
+ done
+0:quoting in [key]=value syntax
+>1first element!
+>first value
+>2second element?
+>second value
+>3third element#
+>third value
+>4fourth element\
+>fourth value


^ permalink raw reply	[flat|nested] 15+ messages in thread

* 'typeset -p' of assocs (was: Re: PATCH: [key]=value syntax, work in progress)
  2017-09-13  9:14   ` Oliver Kiddle
  2017-09-13 19:41     ` Peter Stephenson
@ 2017-09-14  6:54     ` Daniel Shahaf
  1 sibling, 0 replies; 15+ messages in thread
From: Daniel Shahaf @ 2017-09-14  6:54 UTC (permalink / raw)
  To: Zsh hackers list

[-- Attachment #1: Type: text/plain, Size: 1445 bytes --]

Oliver Kiddle wrote on Wed, Sep 13, 2017 at 11:14:04 +0200:
> It would be good to make typeset -p output use this new syntax by default
> for associative arrays because (without other cues like alignment) it
> can be clearer which items in the list are keys and which values.

I've been using a local patch that prints one space between key and its
value, but two spaces between value and the following key:

    % typeset -p color
    typeset -A color=(  00 none  01 bold  02 faint  03 standout  04 underline  05 blink  07 reverse  08 conceal  22 normal  23 no-standout  24 no-underline  25 no-blink  27 no-reverse  28 no-conceal  30 black  31 red  32 green  33 yellow  34 blue  35 magenta  36 cyan  37 white  39 default  40 bg-black  41 bg-red  42 bg-green  43 bg-yellow  44 bg-blue  45 bg-magenta  46 bg-cyan  47 bg-white  49 bg-default  bg-black 40  bg-blue 44  bg-cyan 46  bg-default 49  bg-green 42  bg-grey 40  bg-magenta 45  bg-red 41  bg-white 47  bg-yellow 43  black 30  blink 05  blue 34  bold 01  conceal 08  cyan 36  default 39  faint 02  fg-black 30  fg-blue 34  fg-cyan 36  fg-default 39  fg-green 32  fg-grey 30  fg-magenta 35  fg-red 31  fg-white 37  fg-yellow 33  green 32  grey 30  magenta 35  no-blink 25  no-conceal 28  no-reverse 27  no-standout 23  no-underline 24  none 00  normal 22  red 31  reverse 07  standout 03  underline 04  white 37  yellow 33  )

I think I posted it once, but here it is again.

Cheers,

Daniel

[-- Attachment #2: 0001-Print-two-spaces-in-typeset-p-of-assocs.patch --]
[-- Type: text/x-diff, Size: 1431 bytes --]

>From 714da4bb3a26957cbd65058f7fb4afca85f82ee2 Mon Sep 17 00:00:00 2001
From: Daniel Shahaf <d.s@daniel.shahaf.name>
Date: Wed, 28 Sep 2016 13:15:04 +0000
Subject: [PATCH] Print two spaces in 'typeset -p' of assocs

(tweaked for upstream 39704/0f5e670cde5f844680a20f986786249dfe983584)
---
 Src/params.c         | 6 ++++--
 Test/V10private.ztst | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/Src/params.c b/Src/params.c
index ef72cba44..39af27c40 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -5298,6 +5298,7 @@ printparamvalue(Param p, int printflags)
 	if (!(printflags & PRINT_KV_PAIR)) {
 	    putchar('(');
 	    putchar(' ');
+	    putchar(' ');
 	}
 	{
             HashTable ht = p->gsu.h->getfn(p);
@@ -5309,9 +5310,10 @@ printparamvalue(Param p, int printflags)
 	    putchar(')');
 	break;
     }
-    if (printflags & PRINT_KV_PAIR)
+    if (printflags & PRINT_KV_PAIR) {
 	putchar(' ');
-    else
+	putchar(' ');
+    } else
 	putchar('\n');
 }
 
diff --git a/Test/V10private.ztst b/Test/V10private.ztst
index 7ebf5a87f..d1d9b0887 100644
--- a/Test/V10private.ztst
+++ b/Test/V10private.ztst
@@ -128,7 +128,7 @@
  print ${(kv)hash_test}
 0:private hides value from surrounding scope in nested scope
 >typeset -a hash_test=( top level )
->typeset -A hash_test=( in function )
+>typeset -A hash_test=(  in function  )
 >typeset -g -a hash_test=( top level )
 >array-local top level
 >top level

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-13 19:41     ` Peter Stephenson
@ 2017-09-14  7:03       ` Daniel Shahaf
  2017-09-14 20:38       ` Peter Stephenson
  1 sibling, 0 replies; 15+ messages in thread
From: Daniel Shahaf @ 2017-09-14  7:03 UTC (permalink / raw)
  To: Peter Stephenson; +Cc: Zsh hackers list

Peter Stephenson wrote on Wed, Sep 13, 2017 at 20:41:08 +0100:
> +++ b/Doc/Zsh/params.yo
> @@ -91,13 +91,36 @@ cindex(array assignment)
> +available.  Both var(key) and var(value) undergo all forms of expansion
> +allowed for single word substitutions (this does not include filename
> +generation).

This uses the term "single word substitutions".  The completion for 'e'
after ${(<TAB> says "single word shell expansions".  The manual entry
for ${(e)} does not use either term.

Perhaps the manual for ${(e)} could use the term "single-word
(substitutions|expansions)"?  And then this sentence might
cross-reference that flag; for example:

    Both var(key) and var(value) undergo single word expansions,
    as in `tt(${+LPAR()e+RPAR())var(...)tt(+RPAR())'
    (note, this does not include filename generation).

Cheers,

Daniel


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-13 19:41     ` Peter Stephenson
  2017-09-14  7:03       ` Daniel Shahaf
@ 2017-09-14 20:38       ` Peter Stephenson
  2017-09-16 10:30         ` Daniel Shahaf
  1 sibling, 1 reply; 15+ messages in thread
From: Peter Stephenson @ 2017-09-14 20:38 UTC (permalink / raw)
  To: Zsh hackers list

typeset -p update and other tweaks Daniel wanted (feel free to make your
own doc patches rather than getting me to do the work :-)).  Does look
clearer this way.

pws

diff --git a/Doc/Zsh/expn.yo b/Doc/Zsh/expn.yo
index a61738f..d5e5838 100644
--- a/Doc/Zsh/expn.yo
+++ b/Doc/Zsh/expn.yo
@@ -1025,9 +1025,10 @@ ifnzman(noderef(Filename Expansion))\
 ifzman(the section FILENAME EXPANSION below).
 )
 item(tt(e))(
-Perform em(parameter expansion), em(command substitution) and
-em(arithmetic expansion) on the result. Such expansions can be
-nested but too deep recursion may have unpredictable effects.
+Perform single word shell expansions, namely em(parameter expansion),
+em(command substitution) and em(arithmetic expansion), on the
+result. Such expansions can be nested but too deep recursion may have
+unpredictable effects.
 )
 item(tt(f))(
 Split the result of the expansion at newlines. This is a shorthand
diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 33e9fd6..2534d1c 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -102,13 +102,16 @@ may be in any order.  Note that this syntax is strict: tt([) and tt(]=) must
 not be quoted, while var(key) may not consist of the unquoted string
 tt(]=), but is otherwise treated as a simple string.  Furthermore, all
 elements must match this form or an error is generated; likewise, if the
-first entry does not match this form any later entry that does is taken
+first entry does not match this form, any later entry that does is taken
 as a simple value rather than a key / value pair. The enhanced forms of
 subscript expression that may be used when directly subscripting a
 variable name, described in the section Array Subscripts below, are not
 available.  Both var(key) and var(value) undergo all forms of expansion
-allowed for single word substitutions (this does not include filename
-generation).
+allowed for single word shell expansions (this does not include filename
+generation); these are as performed by the parameter expansion flag
+tt(LPAR()e+RPAR()) as described in
+ifzman(zmanref(zshparam))\
+ifnzman(nodref(Parameter Expansion)).
 
 If no parameter var(name) exists, an ordinary array parameter is created.
 If the parameter var(name) exists and is a scalar, it is replaced by a new
diff --git a/Src/params.c b/Src/params.c
index e0aaaf6..d628ddf 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -5564,9 +5564,7 @@ printparamvalue(Param p, int printflags)
 {
     char *t, **u;
 
-    if (printflags & PRINT_KV_PAIR)
-	putchar(' ');
-    else
+    if (!(printflags & PRINT_KV_PAIR))
 	putchar('=');
 
     /* How the value is displayed depends *
@@ -5721,7 +5719,11 @@ printparamnode(HashNode hn, int printflags)
 	zputs(p->node.nam, stdout);
 	putchar('\n');
     } else {
+	if (printflags & PRINT_KV_PAIR)
+	    putchar('[');
 	quotedzputs(p->node.nam, stdout);
+	if (printflags & PRINT_KV_PAIR)
+	    printf("]=");
 
 	printparamvalue(p, printflags);
     }
diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst
index ae21804..7923ae3 100644
--- a/Test/B02typeset.ztst
+++ b/Test/B02typeset.ztst
@@ -763,6 +763,7 @@
  for key in ${(ok)keyvalhash}; do
    print -l $key $keyvalhash[$key]
  done
+ typeset -p keyvalhash
 0:Substitution in [key]=val syntax
 >*
 >?not_globbed?
@@ -776,3 +777,4 @@
 >another value
 >the key
 >the value
+>typeset -A keyvalhash=( ['*']='?not_globbed?' ['another key']='another value' ['the key']='the value' )
diff --git a/Test/V10private.ztst b/Test/V10private.ztst
index 78ecd48..880784e 100644
--- a/Test/V10private.ztst
+++ b/Test/V10private.ztst
@@ -123,7 +123,7 @@
  print ${(kv)hash_test}
 0:private hides value from surrounding scope in nested scope
 >typeset -a hash_test=( top level )
->typeset -A hash_test=( in function )
+>typeset -A hash_test=( [in]=function )
 >typeset -g -a hash_test=( top level )
 >array-local top level
 >top level


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-13 16:44       ` Bart Schaefer
@ 2017-09-14 20:48         ` Peter Stephenson
  2017-09-19  1:13           ` Bart Schaefer
  0 siblings, 1 reply; 15+ messages in thread
From: Peter Stephenson @ 2017-09-14 20:48 UTC (permalink / raw)
  To: Zsh hackers list

On Wed, 13 Sep 2017 09:44:30 -0700
Bart Schaefer <schaefer@brasslantern.com> wrote:
> (How DO you assign "all file names containing
> an equal sign following a leading digit" to an array in bash/ksh,
> anyway?)

By the way, I don't know about bash/ksh, but in zsh you can quote the =
--- while it never expands at that point it's still a token and quoting
it suppresses the special behaviour.  Feel free to produce a manual
patch if you want (rather than looking at the manual, deciding what
needs to change, then expecting me to do it, which is deprecated
behaviour...)

pws


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-14 20:38       ` Peter Stephenson
@ 2017-09-16 10:30         ` Daniel Shahaf
  0 siblings, 0 replies; 15+ messages in thread
From: Daniel Shahaf @ 2017-09-16 10:30 UTC (permalink / raw)
  To: zsh-workers

Peter Stephenson wrote on Thu, 14 Sep 2017 21:38 +0100:
> typeset -p update and other tweaks Daniel wanted (feel free to make your
> own doc patches rather than getting me to do the work :-)).

Sorry about the load-sharing violation.  I thought you were still editing the
patch; if I'd realised it'd been finalised/committed, I would've posted a patch.

> Does look clearer this way.


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: PATCH: [key]=value syntax, work in progress
  2017-09-14 20:48         ` Peter Stephenson
@ 2017-09-19  1:13           ` Bart Schaefer
  0 siblings, 0 replies; 15+ messages in thread
From: Bart Schaefer @ 2017-09-19  1:13 UTC (permalink / raw)
  To: Zsh hackers list

On Sep 14,  9:48pm, Peter Stephenson wrote:
}
} Feel free to produce a manual patch if you want

I've rearranged your very long paragraph a bit in an attempt to put the
most important information first and make the reading a little bit less
intense.  Added mention of handling of nested parens as well as quoting
of the equal-sign.  Squashed some but perhaps not all passive voice.


diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 37cc256..c675cba 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -93,29 +93,42 @@ indent(tt(set -A) var(name) var(value) ...)
 indent(var(name)tt(=LPAR())var(value) ...tt(RPAR()))
 indent(var(name)tt(=LPAR())tt([)var(key)tt(]=)var(value) ...tt(RPAR()))
 
+If no parameter var(name) exists, an ordinary array parameter is created.
+If the parameter var(name) exists and is a scalar, it is replaced by a new
+array.
+
 In the third form, var(key) is an expression that will be evaluated in
 arithmetic context (in its simplest form, an integer) that gives the
 index of the element to be assigned with var(value).  In this form any
 elements not explicitly mentioned that come before the largest index to
-which a value is assigned will be assigned an empty string. The indices
+which a value is assigned are assigned an empty string.  The indices
 may be in any order.  Note that this syntax is strict: tt([) and tt(]=) must
-not be quoted, while var(key) may not consist of the unquoted string
-tt(]=), but is otherwise treated as a simple string.  Furthermore, all
-elements must match this form or an error is generated; likewise, if the
-first entry does not match this form, any later entry that does is taken
-as a simple value rather than a key / value pair. The enhanced forms of
-subscript expression that may be used when directly subscripting a
+not be quoted, and var(key) may not consist of the unquoted string
+tt(]=), but is otherwise treated as a simple string.  The enhanced forms
+of subscript expression that may be used when directly subscripting a
 variable name, described in the section Array Subscripts below, are not
-available.  Both var(key) and var(value) undergo all forms of expansion
+available.
+
+When assigning with this third form, every element must use this syntax or
+an error is generated.  Likewise, if the first entry does not match this
+form, any later entry that does is taken  as a simple value rather than a
+key / value pair.  Both var(key) and var(value) undergo all forms of expansion
 allowed for single word shell expansions (this does not include filename
 generation); these are as performed by the parameter expansion flag
 tt(LPAR()e+RPAR()) as described in
 ifzman(zmanref(zshparam))\
 ifnzman(noderef(Parameter Expansion)).
+Nested parentheses may surround var(value) and are included as part of the
+value, which is joined into a plain string; this differs from ksh which
+allows the values to themselves be arrays.  A future version of zsh may
+support that.  To cause the brackets to be interpreted as a character
+class for filename generation, and therefore to treat the resulting list
+of files as a set of values, quote the equal sign using any form of quoting.
+Example:
+ifzman()
+indent(var(name)tt(=LPAR())tt([a-z]'='*RPAR()))
 
-If no parameter var(name) exists, an ordinary array parameter is created.
-If the parameter var(name) exists and is a scalar, it is replaced by a new
-array.  To append to an array without changing the existing values, use
+To append to an array without changing the existing values, use
 one of the following:
 ifzman()
 indent(var(name)tt(+=LPAR())var(value) ...tt(RPAR()))


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2017-09-19  1:13 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-09-11 20:51 PATCH: [key]=value syntax, work in progress Peter Stephenson
2017-09-11 20:56 ` Peter Stephenson
2017-09-12 20:25 ` Peter Stephenson
2017-09-12 20:28   ` Peter Stephenson
2017-09-13  7:13   ` Bart Schaefer
2017-09-13  8:53     ` Peter Stephenson
2017-09-13 16:44       ` Bart Schaefer
2017-09-14 20:48         ` Peter Stephenson
2017-09-19  1:13           ` Bart Schaefer
2017-09-13  9:14   ` Oliver Kiddle
2017-09-13 19:41     ` Peter Stephenson
2017-09-14  7:03       ` Daniel Shahaf
2017-09-14 20:38       ` Peter Stephenson
2017-09-16 10:30         ` Daniel Shahaf
2017-09-14  6:54     ` 'typeset -p' of assocs (was: Re: PATCH: [key]=value syntax, work in progress) Daniel Shahaf

Code repositories for project(s) associated with this public inbox

	https://git.vuxu.org/mirror/zsh/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).