zsh-workers
 help / color / mirror / code / Atom feed
From: Peter Stephenson <p.stephenson@samsung.com>
To: Zsh Hackers' List <zsh-workers@zsh.org>
Subject: Re: [key]+=val
Date: Tue, 26 Sep 2017 10:32:54 +0100	[thread overview]
Message-ID: <20170926103254.1670a650@pwslap01u.europe.root.pri> (raw)
In-Reply-To: <CAH+w=7Zb0MWChwWBxGgU17ibMVACoWMsLNtMwETFBwukgZ0yZQ@mail.gmail.com>

On Mon, 25 Sep 2017 10:53:25 -0700
Bart Schaefer <schaefer@brasslantern.com> wrote:
> On Mon, Sep 25, 2017 at 3:35 AM, Peter Stephenson
> <p.stephenson@samsung.com> 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


  reply	other threads:[~2017-09-26  9:33 UTC|newest]

Thread overview: 7+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <CGME20170925103513eucas1p2b7d91180ccc6defe455daca9248ae222@eucas1p2.samsung.com>
2017-09-25 10:35 ` [key]+=val Peter Stephenson
2017-09-25 17:53   ` [key]+=val Bart Schaefer
2017-09-26  9:32     ` Peter Stephenson [this message]
2017-09-26 17:28       ` [key]+=val Bart Schaefer
2017-09-27  8:42         ` [key]+=val Peter Stephenson
2017-09-27  8:59           ` [key]+=val Peter Stephenson
2017-09-27 16:22           ` [key]+=val Peter Stephenson

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170926103254.1670a650@pwslap01u.europe.root.pri \
    --to=p.stephenson@samsung.com \
    --cc=zsh-workers@zsh.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).