zsh-workers
 help / color / mirror / code / Atom feed
From: Bart Schaefer <schaefer@brasslantern.com>
To: Zsh hackers list <zsh-workers@zsh.org>
Subject: [PATCH] Add ${!nameref} in ksh emulation and ${(!)nameref} natively
Date: Sun, 5 Mar 2023 15:27:20 -0800	[thread overview]
Message-ID: <CAH+w=7ap3adBNf9yDczESj0JuruGdMoW0ZfuQpWn834NFtY9uQ@mail.gmail.com> (raw)

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

As discussed in workers/51495 and referrers, this implements expanding
a named reference to the name of its referent rather than the value.

Included are expanded comments documenting other ways that ${!...} is
interpreted in bash, for us to potentially follow in the future.

No doc yet, as this may still be controversial?

[-- Attachment #2: nameref-show.txt --]
[-- Type: text/plain, Size: 4974 bytes --]

diff --git a/Src/params.c b/Src/params.c
index c9f4b3017..85eaee609 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -2144,7 +2144,10 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
 	int isvarat;
 
         isvarat = (t[0] == '@' && !t[1]);
-	pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t);
+	if (flags & SCANPM_NONAMEREF)
+	    pm = (Param) paramtab->getnode2(paramtab, *t == '0' ? "0" : t);
+	else
+	    pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t);
 	if (sav)
 	    *s = sav;
 	*pptr = s;
@@ -2155,7 +2158,7 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
 	    memset(v, 0, sizeof(*v));
 	else
 	    v = (Value) hcalloc(sizeof *v);
-	if (pm->node.flags & PM_NAMEREF) {
+	if ((pm->node.flags & PM_NAMEREF) && !(flags & SCANPM_NONAMEREF)) {
 	    char *refname = GETREFNAME(pm);
 	    if (refname && *refname) {
 		/* only happens for namerefs pointing to array elements */
diff --git a/Src/subst.c b/Src/subst.c
index 7a4b433bc..a86005340 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -1818,14 +1818,14 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
      * Use for the (k) flag.  Goes down into the parameter code,
      * sometimes.
      */
-    char hkeys = 0;
+    int hkeys = 0;
     /*
      * Used for the (v) flag, ditto.  Not quite sure why they're
      * separate, but the tradition seems to be that things only
      * get combined when that makes the result more obscure rather
      * than less.
      */
-    char hvals = 0;
+    int hvals = 0;
     /*
      * Whether we had to evaluate a subexpression, i.e. an
      * internal ${...} or $(...) or plain $pm.  We almost don't
@@ -1870,8 +1870,8 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
      * these later on, too.
      */
     c = *s;
-    if (itype_end(s, INAMESPC, 1) == s && *s != '#' && c != Pound &&
-	!IS_DASH(c) &&
+    if (itype_end(s, (c == Inbrace ? INAMESPC : IIDENT), 1) == s &&
+	*s != '#' && c != Pound && !IS_DASH(c) &&
 	c != '!' && c != '$' && c != String && c != Qstring &&
 	c != '?' && c != Quest &&
 	c != '*' && c != Star && c != '@' && c != '{' &&
@@ -1891,15 +1891,30 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 	s++;
 	/*
 	 * In ksh emulation a leading `!' is a special flag working
-	 * sort of like our (k).
+	 * sort of like our (k).  This is true only for arrays or
+	 * associative arrays and only with subscripts [*] or [@],
+	 * so zsh's implementation is approximate.  For namerefs
+	 * in ksh, ${!ref} substitues the parameter name at the
+	 * end of any chain of references, rather than the value.
+	 *
 	 * TODO: this is one of very few cases tied directly to
 	 * the emulation mode rather than an option.  Since ksh
 	 * doesn't have parameter flags it might be neater to
 	 * handle this with the ^, =, ~ stuff, below.
 	 */
 	if ((c = *s) == '!' && s[1] != Outbrace && EMULATION(EMULATE_KSH)) {
-	    hkeys = SCANPM_WANTKEYS;
+	    hkeys = SCANPM_WANTKEYS|SCANPM_NONAMEREF;
 	    s++;
+	    /* There's a slew of other special bash meanings of parameter
+	     * references that start with "!":
+	     *  ${!name} == ${(P)name} (when name is not a nameref)
+	     *  ${!name*} == ${(k)parameters[(I)name*]}
+	     *  ${!name@} == ${(@k)parameters[(I)name*]}
+	     *  ${!name[*]} == ${(k)name} (but indexes of ordinary arrays, too)
+	     *  ${!name[@]} == ${(@k)name} (ditto, as noted above for ksh)
+	     *
+	     * See also workers/34390, workers/34397, workers/34408.
+	     */
 	} else if (c == '(' || c == Inpar) {
 	    char *t, sav;
 	    int tt = 0;
@@ -2154,10 +2169,19 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
 		    escapes = 1;
 		    break;
 
+		case '!':
+		    if ((hkeys|hvals) & ~SCANPM_NONAMEREF)
+			goto flagerr;
+		    hkeys = SCANPM_NONAMEREF;
+		    break;
 		case 'k':
+		    if (hkeys & ~SCANPM_WANTKEYS)
+			goto flagerr;
 		    hkeys = SCANPM_WANTKEYS;
 		    break;
 		case 'v':
+		    if (hvals & ~SCANPM_WANTKEYS)
+			goto flagerr;
 		    hvals = SCANPM_WANTVALS;
 		    break;
 
@@ -2308,7 +2332,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int pf_flags,
     /*
      * Look for special unparenthesised flags.
      * TODO: could make these able to appear inside parentheses, too,
-     * i.e. ${(^)...} etc.
+     * i.e. ${(^)...} etc., but ${(~)...} already has another meaning.
      */
     for (;;) {
 	if ((c = *s) == '^' || c == Hat) {
diff --git a/Src/zsh.h b/Src/zsh.h
index 0de1f7afb..f3a777045 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -1964,6 +1964,7 @@ struct tieddata {
 #define SCANPM_CHECKING   (1<<10) /* Check if set, no need to create */
 #define SCANPM_NOEXEC     (1<<11) /* No command substitutions, etc. */
 #define SCANPM_NONAMESPC  (1<<12) /* namespace syntax not allowed */
+#define SCANPM_NONAMEREF  (1<<13) /* named references are not followed */
 
 /* "$foo[@]"-style substitution
  * Only sign bit is significant

             reply	other threads:[~2023-03-05 23:28 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-03-05 23:27 Bart Schaefer [this message]
2023-03-07  3:51 ` Bart Schaefer

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='CAH+w=7ap3adBNf9yDczESj0JuruGdMoW0ZfuQpWn834NFtY9uQ@mail.gmail.com' \
    --to=schaefer@brasslantern.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).