Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: [key]+=val



On Mon, 25 Sep 2017 10:53:25 -0700
Bart Schaefer <schaefer@xxxxxxxxxxxxxxxx> wrote:
> On Mon, Sep 25, 2017 at 3:35 AM, Peter Stephenson
> <p.stephenson@xxxxxxxxxxx> wrote:
> >
> > foo=([key]+=val)
> >
> > doesn't (just creates the value), because you're starting with an empty
> > set of values in foo so there's nothing to append to.  Does this sound
> > reasonable?  It's a messy layering violation otherwise so it would need
> > a good reason to do it differently.
> 
> Ksh says:
> 
> $ typeset -A foo
> $ foo=([samekey]+=val [samekey]+=ue [samekey]+=grows)
> $ typeset -p foo
> typeset -A foo=([samekey]=valuegrows)

That's actually not too hard, and in fact it helped me think through a
slightly neater (and less leak-prone) way of updating normal arrays.

What I was worried about was starting off in a context where we're
creating a new array except we're not really creating a new array
because we're digging into an old one...  in this case, we're always
referring to elements in the array we're creating, which I can live
with.

Here's what I've got.  I haven't complicated the documentation by noting
the edge case you mentioned.

pws

diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 430f097..a71dc66 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -145,7 +145,8 @@ 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).
+var(value).  Also, it is possible to use tt([)var(key)tt(]+=)var(value)
+to append to the existing value at that key.
 
 Within the parentheses on the right hand side of either form of the
 assignment, newlines and semicolons are treated the same as white space,
@@ -180,7 +181,9 @@ 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.
+replaces the value for the existing key if it is.  In the second
+form it is also possible to use tt([)var(key)tt(]+=)var(value) to
+append to the existing value at that key.
 
 To create an empty array (including associative arrays), use one of:
 ifzman()
diff --git a/Src/params.c b/Src/params.c
index 4d4d080..3236f71 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -3208,13 +3208,15 @@ assignaparam(char *s, char **val, int flags)
 	     * This is an ordinary array with key / value pairs.
 	     */
 	    int maxlen, origlen, nextind;
-	    char **fullval;
+	    char **fullval, **origptr;
 	    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));
+		origptr = v->pm->gsu.a->getfn(v->pm);
+		maxlen = origlen = arrlen(origptr);
 	    } else {
 		maxlen = origlen = 0;
+		origptr = NULL;
 	    }
 	    nextind = 0;
 	    for (aptr = val; *aptr; ) {
@@ -3245,25 +3247,36 @@ assignaparam(char *s, char **val, int flags)
 	    }
 	    fullval[maxlen] = NULL;
 	    if (flags & ASSPM_AUGMENT) {
-		char **srcptr = v->pm->gsu.a->getfn(v->pm);
+		char **srcptr = origptr;
 		for (aptr = fullval; aptr <= fullval + origlen; aptr++) {
-		    *aptr = ztrdup(*srcptr); 
+		    *aptr = ztrdup(*srcptr);
 		    srcptr++;
 		}
 	    }
 	    iptr = subscripts;
 	    nextind = 0;
 	    for (aptr = val; *aptr; ++aptr) {
+		char *old;
 		if (**aptr == Marker) {
+		    int augment = ((*aptr)[1] == '+');
 		    zsfree(*aptr);
 		    zsfree(*++aptr); /* Index, no longer needed */
-		    fullval[*iptr] = *++aptr;
+		    old = fullval[*iptr];
+		    if (augment && old) {
+			fullval[*iptr] = bicat(old, *++aptr);
+			zsfree(*aptr);
+		    } else {
+			fullval[*iptr] = *++aptr;
+		    }
 		    nextind = *iptr + 1;
 		    ++iptr;
 		} else {
+		    old = fullval[nextind];
 		    fullval[nextind] = *aptr;
 		    ++nextind;
 		}
+		if (old)
+		    zsfree(old);
 		/* aptr now on value in both cases */
 	    }
 	    if (*aptr) {		/* Shouldn't be possible */
@@ -3828,8 +3841,20 @@ arrhashsetfn(Param pm, char **val, int flags)
 	ht = paramtab = newparamtable(17, pm->node.nam);
     }
     for (aptr = val; *aptr; ) {
-	if (**aptr == Marker)
+	int eltflags = 0;
+	if (**aptr == Marker) {
+	    /* Either all elements have Marker or none. Checked in caller. */
+	    if ((*aptr)[1] == '+') {
+		/* Actually, assignstrvalue currently doesn't handle this... */
+		eltflags = ASSPM_AUGMENT;
+		/* ...so we'll use the trick from setsparam(). */
+		v->start = INT_MAX;
+	    } else {
+		v->start = 0;
+	    }
+	    v->end = -1;
 	    zsfree(*aptr++);
+	}
 	/* The parameter name is ztrdup'd... */
 	v->pm = createparam(*aptr, PM_SCALAR|PM_UNSET);
 	/*
@@ -3840,7 +3865,7 @@ arrhashsetfn(Param pm, char **val, int flags)
 	    v->pm = (Param) paramtab->getnode(paramtab, *aptr);
 	zsfree(*aptr++);
 	/* ...but we can use the value without copying. */
-	setstrvalue(v, *aptr++);
+	assignstrvalue(v, *aptr++, eltflags);
     }
     paramtab = opmtab;
     pm->gsu.h->setfn(pm, ht);
diff --git a/Src/subst.c b/Src/subst.c
index 55b5444..eef0dc7 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -53,16 +53,25 @@ keyvalpairelement(LinkList list, LinkNode node)
     if ((start = (char *)getdata(node)) &&
 	start[0] == Inbrack &&
 	(end = strchr(start+1, Outbrack)) &&
-	end[1] == Equals) {
+	/* ..]=value or ]+=Value */
+	(end[1] == Equals ||
+	 (end[1] == '+' && end[2] == Equals))) {
 	static char marker[2] = { Marker, '\0' };
+	static char marker_plus[3] = { Marker, '+', '\0' };
 	*end = '\0';
 
 	dat = start + 1;
 	singsub(&dat);
 	untokenize(dat);
-	setdata(node, marker);
-	node = insertlinknode(list, node, dat);
-	dat = end + 2;
+	if (end[1] == '+') {
+	    setdata(node, marker_plus);
+	    node = insertlinknode(list, node, dat);
+	    dat = end + 3;
+	} else {
+	    setdata(node, marker);
+	    node = insertlinknode(list, node, dat);
+	    dat = end + 2;
+	}
 	singsub(&dat);
 	untokenize(dat);
 	return insertlinknode(list, node, dat);
diff --git a/Src/zsh.h b/Src/zsh.h
index b6e2001..c1138bf 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -228,7 +228,9 @@ struct mathfunc {
  * - In pattern character arrays as guaranteed not to mark a character in
  *   a string.
  * - In assignments with the ASSPM_KEY_VALUE flag set in order to
- *   mark that there is a key / value pair following.
+ *   mark that there is a key / value pair following.  If this
+ *   comes from [key]=value the Marker is followed by a null;
+ *   if from [key]+=value the Marker is followed by a '+' then a null.
  * All the above are local uses --- any case where the Marker has
  * escaped beyond the context in question is an error.
  */
diff --git a/Test/D04parameter.ztst b/Test/D04parameter.ztst
index abac8f7..4f8e76a 100644
--- a/Test/D04parameter.ztst
+++ b/Test/D04parameter.ztst
@@ -2302,3 +2302,39 @@ F:behavior, see http://austingroupbugs.net/view.php?id=888
 >KVA2two
 >KVA3three
 >*
+
+  local -a keyvalarray
+  keyvalarray=(1 2 3)
+  keyvalarray+=([1]+=a [2]=b)
+  print $keyvalarray
+0:Append to element(s) of array
+>1a b 3
+
+  local -A keyvalhash
+  keyvalhash=([a]=a [b]=b [c]=c)
+  keyvalhash+=([a]+=yee [b]=ee)
+  local key val
+  for key in "${(ok)keyvalhash[@]}"; do
+    val=${keyvalhash[$key]}
+    print -r -- $key $val
+  done
+0:Append to element(s) of associative array
+>a ayee
+>b ee
+>c c
+
+  local -A keyvalarray
+  keyvalarray=([1]=who [2]=anyway [1]+=is [1]+=that [1]+=mysterious [1]+=man)
+  print -rl -- ${(kv)keyvalarray}
+0:Append to element of associative array on creation
+>1
+>whoisthatmysteriousman
+>2
+>anyway
+
+  local -A keyvalhash
+  keyvalhash=([one]=hows [one]+=your [one]+=father [one]+=today)
+  print -rl -- ${(kv)keyvalhash}
+0:Append to element of associative array on creation
+>one
+>howsyourfathertoday



Messages sorted by: Reverse Date, Date, Thread, Author