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

[PATCH] Implement references to positional parameters



References to positional parameters are currently accepted but they aren't implemented. Such references behave like ones to not-yet-defined variables. The patch fixes this. One can then define "typeset -n ref=2" and use "$ref" to obtain "$2" or "ref=foo" to do "2=foo". Unsetting "ref" with "unset ref" has the same effect as "2=()". Assigning "ref" with an array value is however not possible; there is no "ref" equivalent of "2=(...)".

- Implement references to positional parameters

Note that the patch is built on top of workers/54300.

Philippe

diff --git a/Src/builtin.c b/Src/builtin.c
index 7c095149d..83a52d3ab 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -3857,13 +3857,10 @@ bin_unset(char *name, char **argv, Options ops, int func)
 		    for (pm = (Param) paramtab->nodes[i]; pm; pm = next) {
 			/* record pointer to next, since we may free this one */
 			next = (Param) pm->node.next;
-			if ((!(pm->node.flags & PM_RESTRICTED) ||
-			     unset(RESTRICTED)) &&
-			    pattry(pprog, pm->node.nam)) {
-			    if (!OPT_ISSET(ops,'n') &&
-				(pm->node.flags & PM_NAMEREF) && pm->u.str)
-				unsetparam(pm->u.str);
-			    else
+			if (pattry(pprog, pm->node.nam)) {
+			    if (OPT_ISSET(ops,'n') ||
+				((pm = resolve_nameref(pm)) &&
+				 !(pm->node.flags & PM_NAMEREF)))
 				unsetparam_pm(pm, 0, 1);
 			    match++;
 			}
@@ -3912,10 +3909,7 @@ bin_unset(char *name, char **argv, Options ops, int func)
 	 */
 	if (!pm)
 	    continue;
-	else if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
-	    zerrnam(name, "%s: restricted", pm->node.nam);
-	    returnval = 1;
-	} else if (ss) {
+	else if (ss) {
 	    if ((pm->node.flags & PM_NAMEREF) &&
 		(!(pm = resolve_nameref(pm)) || pm->width)) {
 		/* warning? */
@@ -3959,22 +3953,11 @@ bin_unset(char *name, char **argv, Options ops, int func)
 		zerrnam(name, "%s: invalid element for unset", s);
 		returnval = 1;
 	    }
-	} else {
-	    if (!OPT_ISSET(ops,'n')) {
-		int ref = (pm->node.flags & PM_NAMEREF);
-		if (!(pm = resolve_nameref(pm)))
-		    continue;
-		if (ref && pm->level < locallevel &&
-		    !(pm->node.flags & PM_READONLY)) {
-		    /* Just mark unset, do not remove from table */
-		    stdunsetfn(pm, 0);
-		    pm->node.flags |= PM_DECLARED;
-		    continue;
-		}
-	    }
+	} else if (OPT_ISSET(ops,'n') ||
+		   ((pm = resolve_nameref(pm)) &&
+		    !(pm->node.flags & PM_NAMEREF)))
 	    if (unsetparam_pm(pm, 0, 1))
 		returnval = 1;
-	}
 	if (ss)
 	    *ss = '[';
     }
diff --git a/Src/params.c b/Src/params.c
index 461e02acf..516c6bcde 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -224,6 +224,8 @@ static const struct gsu_integer ttyidle_gsu =
 
 static const struct gsu_scalar argzero_gsu =
 { argzerogetfn, argzerosetfn, nullunsetfn };
+static const struct gsu_scalar argn_gsu =
+{ argngetfn, argnsetfn, argnunsetfn };
 static const struct gsu_scalar username_gsu =
 { usernamegetfn, usernamesetfn, stdunsetfn };
 static const struct gsu_scalar dash_gsu =
@@ -484,6 +486,8 @@ static initparam argvparam_pm = IPDEF9("", &pparams, NULL, \
 			  (zsfree((PM)->u.str), (PM)->u.str = (S)))
 
 static Param argvparam;
+static Param *argnparams;
+static size_t argnparams_size;
 
 /*
  * Lists of references to nested variables ("Param" instances) indexed
@@ -499,6 +503,16 @@ static Param argvparam;
  * times in the same list. Non of that is harmful as long as only
  * instances that are still references referring to the ending scope
  * are updated when the scope ends.
+ *
+ * The list corresponding to the global scope never receives any of
+ * the named references described above. Instead, it's used to track
+ * global parameters that were unset via a named reference while in a
+ * scope where they were hidden by a nested parameter with the same
+ * name. In such cases, the global parameter's Param instance can't be
+ * deleted as usual. Instead, it's marked as unset and added to the
+ * global scope's list. Each time a scope ends, the list is traversed
+ * and parameters that are still unset but no longer hidden are
+ * deleted.
  */
 static LinkList *scoperefs = NULL;
 static int scoperefs_num = 0;
@@ -846,6 +860,9 @@ createparamtable(void)
     }
 
     argvparam = (Param) &argvparam_pm;
+    argnparams = zshcalloc(8 * sizeof(Param));
+    argnparams_size = 8;
+    argnparams[0] = (Param) paramtab->getnode(paramtab, "0");
 
     noerrs = 2;
 
@@ -3934,6 +3951,24 @@ unsetparam_pm(Param pm, int altflag, int exp)
 	(pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL)
 	return 0;
 
+    /*
+     * Global variables can only be deleted if they aren't hidden by a
+     * local one with the same name.
+     */
+    if (!pm->level &&
+	pm != (Param) (paramtab == realparamtab ?
+		       /* getnode2() to avoid autoloading */
+		       paramtab->getnode2(paramtab, pm->node.nam) :
+		       paramtab->getnode(paramtab, pm->node.nam))) {
+	LinkList refs;
+	if (!scoperefs)
+	    scoperefs = zshcalloc((scoperefs_num = 8) * sizeof(refs));
+	if (!scoperefs[0])
+	    scoperefs[0] = znewlinklist();
+	zpushnode(scoperefs[0], pm);
+	return 0;
+    }
+
     /* remove parameter node from table */
     paramtab->removenode(paramtab, pm->node.nam);
 
@@ -4980,6 +5015,49 @@ argzerogetfn(UNUSED(Param pm))
     return argzero;
 }
 
+/* Function to get value for positional parameters */
+
+/**/
+static char *
+argngetfn(Param pm)
+{
+    return arrlen_gt(pparams, pm->u.val - 1) ?
+	pparams[pm->u.val - 1] : (char *) hcalloc(1);
+}
+
+/* Function to set value for positional parameters */
+
+/**/
+static void
+argnsetfn(Param pm, char *x)
+{
+    int len = arrlen(pparams);
+    int ppar = pm->u.val;
+    if (ppar <= len)
+	zsfree(pparams[ppar - 1]);
+    else if (x) {
+	int i;
+	pparams = (char **) zrealloc(pparams, sizeof(char *) * ppar + 1);
+	for (i = len; i < ppar - 1; i++)
+	    pparams[i] = ztrdup("");
+	pparams[ppar] = 0;
+    }
+    if (x) {
+	pparams[ppar - 1] = ztrdup(x);
+	zsfree(x);
+    } else if (ppar <= len)
+	memmove(pparams + ppar - 1, pparams + ppar, (len - ppar + 1) * sizeof(char *));
+}
+
+/* Function to unset positional parameters */
+
+/**/
+static void
+argnunsetfn(Param pm, UNUSED(int exp))
+{
+    argnsetfn(pm, NULL);
+}
+
 /* Function to get value for special parameter `HISTSIZE' */
 
 /**/
@@ -5909,6 +5987,15 @@ endparamscope(void)
 	    setscope(pm);
 	}
     }
+    /* Delete unset global variables that were hidden at unset time */
+    if ((refs = scoperefs ? scoperefs[0] : NULL)) {
+	scoperefs[0] = NULL;
+	for (Param pm; refs && (pm = (Param)getlinknode(refs));) {
+	    if ((pm->node.flags & PM_UNSET) && !(pm->node.flags & PM_DECLARED))
+		unsetparam_pm(pm, 1, 0);
+	}
+	freelinklist(refs, NULL);
+    }
     unqueue_signals();
 }
 
@@ -6368,6 +6455,23 @@ resolve_nameref_rec(Param pm, const Param stop, int keep_lastref)
 	    pm = resolve_nameref_rec(pm, stop, keep_lastref);
 	    ref->node.flags &= ~PM_TAGGED;
 	}
+    } else if (idigit(*refname)) {
+	int ppar = zstrtol(refname, NULL, 10);
+	if (ppar >= argnparams_size) {
+	    size_t old_size = argnparams_size;
+	    size_t new_size = argnparams_size = MAX(2 * old_size, ppar);
+	    argnparams = zrealloc(argnparams, new_size * sizeof(Param));
+	    memset(argnparams + old_size, 0,
+		   (new_size - old_size) * sizeof(Param));
+	}
+	if (!(pm = argnparams[ppar])) {
+	    pm = argnparams[ppar] = zshcalloc(sizeof(*pm));
+	    pm->node.nam = zalloc(snprintf(NULL, 0, "%d", ppar) + 1);
+	    sprintf(pm->node.nam, "%d", ppar);
+	    pm->node.flags = PM_SCALAR | PM_SPECIAL;
+	    pm->u.val = ppar;
+	    pm->gsu.s = &argn_gsu;
+	}
     } else if (keep_lastref)
 	pm = ref;
     unqueue_signals();
diff --git a/Test/K01nameref.ztst b/Test/K01nameref.ztst
index 0b4475827..6ab3ddcb7 100644
--- a/Test/K01nameref.ztst
+++ b/Test/K01nameref.ztst
@@ -1026,15 +1026,15 @@ F:Checking for a bug in zmodload that affects later tests
  typeset -p .K01.{scalar,assoc,array,integer,double,float,readonly}
  unset .K01.{scalar,assoc,array,integer,double,float}
 0:unset various types via nameref, including a readonly special
->typeset -g .K01.scalar
->typeset -g -A .K01.assoc
->typeset -g -a .K01.array
->typeset -g -i .K01.integer
->typeset -g -E .K01.double
->typeset -g -F .K01.float
 >typeset -g -r .K01.readonly=RO
 *?*read-only variable: ARGC
 *?*read-only variable: .K01.readonly
+*?*no such variable: .K01.scalar
+*?*no such variable: .K01.assoc
+*?*no such variable: .K01.array
+*?*no such variable: .K01.integer
+*?*no such variable: .K01.double
+*?*no such variable: .K01.float
 
  unset -n ref
  unset one
@@ -1339,14 +1339,14 @@ F:previously this could create an infinite recursion and crash
 
  edgelocal() ( local -n x=$1; typeset -p x; print -r $x )
  edgeupper() ( local -nu x=$1; typeset -p x; print -r $x )
- for edge in argv ARGC \@ \* \# 0 1 01 \! \? - _
+ for edge in argv ARGC \@ \* \# 0 00 1 01 \! \? - _
  do
   edgelocal $edge
   edgelocal "$edge""[1]"
   edgeupper $edge
  done
 0:references to builtin specials
-F:Subscripting on 1 01 ! ? - should print first character but do not
+F:Subscripting on 00 1 01 ! ? - should print first character but do not
 >typeset -n x=argv
 >argv
 >typeset -n x='argv[1]'
@@ -1359,12 +1359,16 @@ F:Subscripting on 1 01 ! ? - should print first character but do not
 >edgelocal
 >typeset -n x='0[1]'
 >e
->typeset -n x=1
+>typeset -n x=00
+>edgelocal
+>typeset -n x='00[1]'
 >
+>typeset -n x=1
+>1
 >typeset -n x='1[1]'
 >
 >typeset -n x=01
->
+>01
 >typeset -n x='01[1]'
 >
 >typeset -n x=!
@@ -1403,6 +1407,7 @@ F:Subscripting on 1 01 ! ? - should print first character but do not
 ?edgelocal: invalid name reference: #[1]
 ?edgeupper: invalid name reference: #
 ?edgeupper: invalid name reference: 0
+?edgeupper: invalid name reference: 00
 ?edgeupper: invalid name reference: 1
 ?edgeupper: invalid name reference: 01
 
@@ -1418,6 +1423,75 @@ F:$$[1] reference should print the first digit of $$ but prints nothing
 >$$[1]
 >$$
 
+ tst() {
+   typeset -n ref0=0 2>&1
+   typeset -n ref00=00 2>&1
+   typeset -n ref2=2 2>&1
+   typeset -n ref02=02 2>&1
+   echo ${(q)0} ${(q)*} - $ref0 - $ref00 - $ref2 - $ref02
+   ref0=TST
+   ref2=BBB
+   echo ${(q)0} ${(q)*} - $ref0 - $ref00 - $ref2 - $ref02
+   ref00=Tst
+   ref02=Bbb
+   echo ${(q)0} ${(q)*} - $ref0 - $ref00 - $ref2 - $ref02; typeset -p -- 0 00 2 02 2>&1
+   unset ref0
+   unset ref2
+   echo ${(q)0} ${(q)*} - $ref0 - $ref00 - $ref2 - $ref02; typeset -p -- 0 00 2 02 2>&1
+   ref0=tst
+   ref2=bbb
+   echo ${(q)0} ${(q)*} - $ref0 - $ref00 - $ref2 - $ref02; typeset -p -- 0 00 2 02 2>&1
+   { ref0=(TST TST) 2>&1 } always { TRY_BLOCK_ERROR=0 }
+   { ref2=(BBB BBB) 2>&1 } always { TRY_BLOCK_ERROR=0 }
+   echo ${(q)0} ${(q)*} - $ref0 - $ref00 - $ref2 - $ref02; typeset -p -- 0 00 2 02 2>&1
+   typeset -n ref5=5 2>&1
+   ref5=zzz
+   echo ${(q)0} ${(q)*}
+   unset ref2
+   echo ${(q)0} ${(q)*}
+   unset ref2
+   echo ${(q)0} ${(q)*}
+   unset ref2
+   echo ${(q)0} ${(q)*}
+   unset ref2
+   echo ${(q)0} ${(q)*}
+   unset ref2
+   echo ${(q)0} ${(q)*}
+ }
+ tst aaa bbb ccc
+ unfunction tst
+0:references to positional parameters
+>tst aaa bbb ccc - tst - tst - bbb - bbb
+>TST aaa BBB ccc - TST - TST - BBB - BBB
+>Tst aaa Bbb ccc - Tst - Tst - Bbb - Bbb
+>tst:typeset:11: no such variable: 00
+>tst:typeset:11: no such variable: 2
+>tst:typeset:11: no such variable: 02
+>typeset -g 0=Tst
+>Tst aaa ccc - Tst - Tst - ccc - ccc
+>tst:typeset:14: no such variable: 00
+>tst:typeset:14: no such variable: 2
+>tst:typeset:14: no such variable: 02
+>typeset -g 0=Tst
+>tst aaa bbb - tst - tst - bbb - bbb
+>tst:typeset:17: no such variable: 00
+>tst:typeset:17: no such variable: 2
+>tst:typeset:17: no such variable: 02
+>typeset -g 0=tst
+>tst:18: 0: attempt to assign array value to non-array
+>tst:19: 2: attempt to assign array value to non-array
+>tst aaa bbb - tst - tst - bbb - bbb
+>tst:typeset:20: no such variable: 00
+>tst:typeset:20: no such variable: 2
+>tst:typeset:20: no such variable: 02
+>typeset -g 0=tst
+>tst aaa bbb '' '' zzz
+>tst aaa '' '' zzz
+>tst aaa '' zzz
+>tst aaa zzz
+>tst aaa
+>tst aaa
+
 #
 # The following tests are run in interactive mode, using PS1 as an
 # assignable special with side-effects.  This crashed at one time.
@@ -1933,6 +2007,257 @@ F:converting from association/array to string should work here too
 ># d:reference to not-yet-defined - local - ref1
 >typeset -i var=42
 
+ test-unset() {
+   typeset var0=foo
+   typeset -n ref1=var0 ref2=ref1
+   typeset cmd=(unset $@); echo "#" $cmd; $cmd
+   typeset -p var0 ref1 ref2
+ }
+ test-unset -n    ref1
+ test-unset -n    ref2
+ test-unset -n -m ref1
+ test-unset -n -m ref2
+ unfunction test-unset
+0:unsetting references with -n unsets the references
+># unset -n ref1
+>typeset var0=foo
+>typeset -n ref2=ref1
+># unset -n ref2
+>typeset var0=foo
+>typeset -n ref1=var0
+># unset -n -m ref1
+>typeset var0=foo
+>typeset -n ref2=ref1
+># unset -n -m ref2
+>typeset var0=foo
+>typeset -n ref1=var0
+
+ test-unset() {
+   typeset var0=foo
+   typeset -n ref1=var0 ref2=ref1
+   typeset cmd=(unset $@); echo "#" $cmd; $cmd
+   typeset -p var0 ref1 ref2
+ }
+ test-unset    ref1
+ test-unset    ref2
+ test-unset -m ref1
+ test-unset -m ref2
+ unfunction test-unset
+0:unsetting references without -n unsets the referred parameters
+># unset ref1
+>typeset -n ref1=var0
+>typeset -n ref2=ref1
+># unset ref2
+>typeset -n ref1=var0
+>typeset -n ref2=ref1
+># unset -m ref1
+>typeset -n ref1=var0
+>typeset -n ref2=ref1
+># unset -m ref2
+>typeset -n ref1=var0
+>typeset -n ref2=ref1
+
+ test-unset() {
+   typeset var0=12345
+   typeset -n ref1=var0 ref2=ref1
+   typeset cmd=(unset $@); echo "#" $cmd; $cmd
+   typeset -p var0
+ }
+ test-unset    ref1"[3]"
+ test-unset    ref2"[3]"
+ test-unset -n ref1"[3]"
+ test-unset -n ref2"[3]"
+ unfunction test-unset
+0:unsetting subscripted references unsets the referred elements
+># unset ref1[3]
+>typeset var0=1245
+># unset ref2[3]
+>typeset var0=1245
+># unset -n ref1[3]
+>typeset var0=1245
+># unset -n ref2[3]
+>typeset var0=1245
+
+ test-unset() {
+   typeset -r var=foo
+   typeset -n ref=var
+   typeset cmd=(unset $@); echo "#" $cmd; { $cmd 2>&1 } always { TRY_BLOCK_ERROR=0 }
+   typeset -p var
+ }
+ test-unset    var
+ test-unset -m var
+ test-unset    ref
+ test-unset -m ref
+ test-unset    var"[2]"
+ test-unset    ref"[2]"
+ test-unset -n ref"[2]"
+ unfunction test-unset
+0:unsetting read-only parameter triggers an error
+># unset var
+>test-unset:3: read-only variable: var
+>typeset -r var=foo
+># unset -m var
+>test-unset:3: read-only variable: var
+>typeset -r var=foo
+># unset ref
+>test-unset:3: read-only variable: var
+>typeset -r var=foo
+># unset -m ref
+>test-unset:3: read-only variable: var
+>typeset -r var=foo
+># unset var[2]
+>test-unset:3: read-only variable: var
+>typeset -r var=foo
+># unset ref[2]
+>test-unset:3: read-only variable: var
+>typeset -r var=foo
+># unset -n ref[2]
+>test-unset:3: read-only variable: var
+>typeset -r var=foo
+
+ test-unset() (
+   setopt restricted
+   typeset -n ifs=IFS
+   typeset cmd=(unset $@); echo "#" $cmd; { $cmd 2>&1 } always { TRY_BLOCK_ERROR=0 }
+   typeset -p IFS
+ )
+ test-unset    IFS
+ test-unset -m IFS
+ test-unset    ifs
+ test-unset -m ifs
+ test-unset    IFS"[2]"
+ test-unset    ifs"[2]"
+ test-unset -n ifs"[2]"
+ unfunction test-unset
+0:unsetting restricted parameter triggers an error
+># unset IFS
+>test-unset:3: IFS: restricted
+>typeset -g IFS=$' \t\n\C-@'
+># unset -m IFS
+>test-unset:3: IFS: restricted
+>typeset -g IFS=$' \t\n\C-@'
+># unset ifs
+>test-unset:3: IFS: restricted
+>typeset -g IFS=$' \t\n\C-@'
+># unset -m ifs
+>test-unset:3: IFS: restricted
+>typeset -g IFS=$' \t\n\C-@'
+># unset IFS[2]
+>test-unset:3: IFS: restricted
+>typeset -g IFS=$' \t\n\C-@'
+># unset ifs[2]
+>test-unset:3: IFS: restricted
+>typeset -g IFS=$' \t\n\C-@'
+># unset -n ifs[2]
+>test-unset:3: IFS: restricted
+>typeset -g IFS=$' \t\n\C-@'
+
+ test-unset() {
+   typeset -n ref1 ref2=ref1
+   typeset cmd=(unset $@); echo "#" $cmd; $cmd
+   typeset -p ref1 ref2
+ }
+ test-unset    ref1
+ test-unset    ref2
+ test-unset -m ref1
+ test-unset -m ref2
+ unfunction test-unset
+0:unsetting placeholder references or their referents has no effect
+># unset ref1
+>typeset -n ref1
+>typeset -n ref2=ref1
+># unset ref2
+>typeset -n ref1
+>typeset -n ref2=ref1
+># unset -m ref1
+>typeset -n ref1
+>typeset -n ref2=ref1
+># unset -m ref2
+>typeset -n ref1
+>typeset -n ref2=ref1
+
+ test-unset() {
+   typeset -n ref1=undefined ref2=ref1
+   typeset cmd=(unset $@); echo "#" $cmd; $cmd
+   typeset -p ref1 ref2
+ }
+ typeset -p undefined 2>&1
+ test-unset    ref1
+ test-unset    ref2
+ test-unset -m ref1
+ test-unset -m ref2
+ unfunction test-unset
+0:unsetting references to not-yet-defined variables or their referents has no effect
+>(eval):typeset:6: no such variable: undefined
+># unset ref1
+>typeset -n ref1=undefined
+>typeset -n ref2=ref1
+># unset ref2
+>typeset -n ref1=undefined
+>typeset -n ref2=ref1
+># unset -m ref1
+>typeset -n ref1=undefined
+>typeset -n ref2=ref1
+># unset -m ref2
+>typeset -n ref1=undefined
+>typeset -n ref2=ref1
+
+ test-unset() {
+   typeset -n refg1=g1 refl1=l1
+   () {
+     typeset -g g1=glb1 g2=glb2
+     typeset    l1=lcl1 l2=lcl2
+     () {
+       typeset -n refg2=g2 refl2=l2
+       typeset cmd=(unset $@ refg1 refg2 refl1 refl2); echo "#" $cmd; $cmd
+     } $@
+     typeset -p g1 g2 l1 l2 2>&1
+   } $@
+   unset g1 g2
+ }
+ test-unset
+ test-unset -m
+ unfunction test-unset
+0:unsetting references referring to parameters in enclosing scopes unsets the parameters
+># unset refg1 refg2 refl1 refl2
+>(anon):typeset:7: no such variable: g1
+>(anon):typeset:7: no such variable: g2
+># unset -m refg1 refg2 refl1 refl2
+>(anon):typeset:7: no such variable: g1
+>(anon):typeset:7: no such variable: g2
+
+ test-unset() {
+   typeset -g g=glb
+   typeset    l=lcl
+   typeset -n refg=g refl=l
+   () {
+     typeset g=hide-g
+     typeset l=hide-l
+     typeset cmd=(unset $@ refg refl); echo "#" $cmd; $cmd
+     echo "# inner scope"
+     typeset -p g l 2>&1
+   } $@
+   echo "# outer scope"
+   typeset -p g l 2>&1
+   unset g
+ }
+ test-unset
+ test-unset -m
+ unfunction test-unset
+0:unsetting references referring to hidden parameters unsets the hidden parameters
+># unset refg refl
+># inner scope
+>typeset g=hide-g
+>typeset l=hide-l
+># outer scope
+>test-unset:typeset:12: no such variable: g
+># unset -m refg refl
+># inner scope
+>typeset g=hide-g
+>typeset l=hide-l
+># outer scope
+>test-unset:typeset:12: no such variable: g
+
  typeset -n ref1
  typeset -n ref2
  typeset -n ref3=ref2


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