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

PATCH: pws-25: local special parameters



This patch makes it possible to make special parameters local, while
remaining special.  What's more, it does it in a way which is supposed to
be consistent and transparent.  The road to hell is paved with good
intentions.

The main point to note is that only the global value can be in the
environment --- strictly, it is possible for a local parameter to be in the
environment with ALLEXPORT if there was no global parameter, but that can't
happen with specials.  This is entirely consistent with ksh, and with the
current rule for normal parameters in zsh.  I discovered a couple of
glitches with the way this was working; for example, typeset -x did put the
parameter in the environment, but as there was no code for restoring the
original environment value it was lost at end of scope.

The possible problems with dual specials like path/PATH are hair-raising.
I think I've solved them in the most natural way, but it will need some
testing.

You should now be able to do things like:

fn() { 
  typeset PATH=/extra/bit:$PATH; 
  # remember, external programmes here see global PATH
  ... 
}

fn() { typeset IFS=:; read -a arr < colonfile; }

fn() { 
 # you'd better be root to begin with
 typeset USERNAME=juser
 ...
}

The only possible compatibility problem I can think of is if someone was
using typeset inside a function to change some minor parameter attribute,
relying on it not becoming local; this seems fairly unlikely.  You will now
need the -g flag to get the same effect.

--- Doc/Zsh/builtins.yo.ns	Sun Jul  4 17:25:25 1999
+++ Doc/Zsh/builtins.yo	Tue Jul  6 13:49:13 1999
@@ -924,7 +924,8 @@
 ifnzman(noderef(Local Parameters))\
 .  Local parameters are not exported unless tt(ALL_EXPORT) is set, in
 which case the parameter is exported em(only) when var(name) does not
-already appear in the environment.
+already exist.  The same rules apply to special shell parameters, which
+retain their special attributes when made local.
 
 For each var(name)tt(=)var(value) assignment, the parameter
 var(name) set to var(value).  Note that arrays currently cannot be
--- Doc/Zsh/params.yo.ns	Mon Jun 28 19:01:59 1999
+++ Doc/Zsh/params.yo	Tue Jul  6 14:01:41 1999
@@ -188,8 +188,7 @@
 (Parameters are dynamically scoped.)  The tt(typeset) builtin, and its
 alternative forms tt(declare), tt(integer), tt(local) and tt(readonly)
 (but not tt(export)), can be used to declare a parameter as being local
-to the innermost scope.  Note that em(special) parameters cannot be made
-local.
+to the innermost scope.
 
 When a parameter is read or assigned to, the
 innermost existing parameter of that name is used.  (That is, the
@@ -200,6 +199,23 @@
 Local parameters disappear when their scope ends.
 tt(unset) can be used to delete a parameter while it is still in scope;
 any outer parameter of the same name remains hidden.
+
+Special parameters may also be made local; they retain their special
+attributes.  This may have unexpected effects.  Firstly, there is no
+default value, so if there is no assigment at the point the variable is
+made local, it will be set to an empty value (or zero in the case of
+integers).  Secondly, special parameters which are made local will not be
+exported (as with other parameters), so that the global value of the
+parameter remains present in the environment if it is already there.  This
+should be particularly noted in the case of tt(PATH): the shell will use
+the local version of tt(PATH) for finding programmes, but programmes using
+the shell's environment will inherit the global version.  The following:
+
+example(typeset PATH=/new/directory:$PATH)
+
+is valid for temporarily allowing the shell to find the programs in
+tt(/new/directory) inside a function.
+
 texinode(Parameters Set By The Shell)(Parameters Used By The Shell)(Local Parameters)(Parameters)
 sect(Parameters Set By The Shell)
 The following parameters are automatically set by the shell:
--- Src/builtin.c.ns	Sun Jul  4 17:24:14 1999
+++ Src/builtin.c	Tue Jul  6 14:29:18 1999
@@ -645,12 +645,14 @@
     setsparam("OLDPWD", ztrdup(oldpwd));
 
     pm = (Param) paramtab->getnode(paramtab, "PWD");
-    if (!(pm->flags & PM_EXPORTED)) {
+    if (!(pm->flags & PM_EXPORTED) &&
+	(!pm->level || (isset(ALLEXPORT) && !pm->old))) {
 	pm->flags |= PM_EXPORTED;
 	pm->env = addenv("PWD", pwd);
     }
     pm = (Param) paramtab->getnode(paramtab, "OLDPWD");
-    if (!(pm->flags & PM_EXPORTED)) {
+    if (!(pm->flags & PM_EXPORTED) &&
+	(!pm->level || (isset(ALLEXPORT) && !pm->old))) {
 	pm->flags |= PM_EXPORTED;
 	pm->env = addenv("OLDPWD", oldpwd);
     }
@@ -1492,7 +1494,7 @@
 typeset_single(char *cname, char *pname, Param pm, int func,
 	       int on, int off, int roff, char *value, Param altpm)
 {
-    int usepm, tc, keeplocal = 0;
+    int usepm, tc, keeplocal = 0, newspecial = 0;
 
     /*
      * Do we use the existing pm?  Note that this isn't the end of the
@@ -1503,22 +1505,31 @@
      */
     usepm = pm && !(pm->flags & PM_UNSET);
 
-    /* Always use an existing pm if special at current locallevel */
-    if (pm && (pm->flags & PM_SPECIAL) && pm->level == locallevel)
+    /*
+     * We need to compare types with an existing pm if special,
+     * even if that's unset
+     */
+    if (pm && (pm->flags & PM_SPECIAL))
 	usepm = 1;
 
     /*
-     * Don't use a non-special existing param if
+     * Don't use an existing param if
      *   - the local level has changed, and
      *   - we are really locallizing the parameter
      */
-    if (usepm && !(pm->flags & PM_SPECIAL) &&
-	locallevel != pm->level && (on & PM_LOCAL))
+    if (usepm && locallevel != pm->level && (on & PM_LOCAL)) {
+	/*
+	 * If the original parameter was special and we're creating
+	 * a new one, we need to keep it special.
+	 */
+	newspecial = (pm->flags & PM_SPECIAL);
 	usepm = 0;
+    }
 
     /* attempting a type conversion, or making a tied colonarray? */
-    if ((tc = usepm && (((off & pm->flags) | (on & ~pm->flags)) &
-			(PM_INTEGER|PM_HASHED|PM_ARRAY|PM_TIED|PM_AUTOLOAD))))
+    if ((tc = (usepm || newspecial)
+	 && (((off & pm->flags) | (on & ~pm->flags)) &
+	     (PM_INTEGER|PM_HASHED|PM_ARRAY|PM_TIED|PM_AUTOLOAD))))
 	usepm = 0;
     if (tc && (pm->flags & PM_SPECIAL)) {
 	zerrnam(cname, "%s: can't change type of a special parameter",
@@ -1526,13 +1537,27 @@
 	return NULL;
     }
 
+    /*
+     * According to the manual, local parameters don't get exported.
+     * A parameter will be local if
+     * 1. we are re-using an existing local parameter
+     *    or
+     * 2. we are not using an existing parameter, but
+     *   i. there is already a parameter, which will be hidden
+     *     or
+     *   ii. we are creating a new local parameter
+     */
+    if ((usepm && pm->level) ||
+	(!usepm && (pm || (locallevel && (on & PM_LOCAL)))))
+	on &= ~PM_EXPORTED;
+
     if (usepm) {
 	on &= ~PM_LOCAL;
 	if (!on && !roff && !value) {
 	    paramtab->printnode((HashNode)pm, 0);
 	    return pm;
 	}
-	if ((pm->flags & PM_RESTRICTED && isset(RESTRICTED))) {
+	if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
 	    zerrnam(cname, "%s: restricted", pname, 0);
 	    return pm;
 	}
@@ -1553,7 +1578,8 @@
 	    if (pm->flags & PM_EXPORTED) {
 		if (!(pm->flags & PM_UNSET) && !pm->env && !value)
 		    pm->env = addenv(pname, getsparam(pname));
-	    } else if (pm->env) {
+	    } else if (pm->env &&
+		       (!pm->level || (isset(ALLEXPORT) && !pm->old))) {
 		delenv(pm->env);
 		zsfree(pm->env);
 		pm->env = NULL;
@@ -1593,13 +1619,68 @@
 	pname = dupstring(pname);
 	unsetparam_pm(pm, 0, 1);
     }
-    /*
-     * Create a new node for a parameter with the flags in `on' minus the
-     * readonly flag
-     */
-    pm = createparam(pname, on & ~PM_READONLY);
-    DPUTS(!pm, "BUG: parameter not created");
-    pm->ct = auxlen;
+
+    if (newspecial) {
+	Param tpm, pm2;
+	if ((pm->flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+	    zerrnam(cname, "%s: restricted", pname, 0);
+	    return pm;
+	}
+	/*
+	 * For specials, we keep the same struct but zero everything.
+	 * Maybe it would be easier to create a new struct but copy
+	 * the get/set methods.
+	 */
+	tpm = (Param) zalloc(sizeof *tpm);
+
+	tpm->nam = pm->nam;
+	if (pm->ename &&
+	    (pm2 = (Param) paramtab->getnode(paramtab, pm->ename)) &&
+	    pm2->level == locallevel) {
+	    /* This is getting silly, but anyway:  if one of a path/PATH
+	     * pair has already been made local at the current level, we
+	     * have to make sure that the other one does not have its value
+	     * saved:  since that comes from an internal variable it will
+	     * already reflect the local value, so restoring it on exit
+	     * would be wrong.
+	     *
+	     * This problem is also why we make sure we have a copy
+	     * of the environment entry in tpm->env, rather than relying
+	     * on the restored value to provide it.
+	     */
+	    tpm->flags = pm->flags | PM_NORESTORE;
+	} else {
+	    copyparam(tpm, pm, 1);
+	}
+	tpm->old = pm->old;
+	tpm->level = pm->level;
+	tpm->ct = pm->ct;
+	tpm->env = pm->env;
+
+	pm->old = tpm;
+	/*
+	 * The remaining on/off flags should be harmless to use,
+	 * because we've checked for unpleasant surprises above.
+	 */
+	pm->flags = (PM_TYPE(pm->flags) | on | PM_SPECIAL) & ~off;
+	/*
+	 * Final tweak: if we've turned on one of the flags with
+	 * numbers, we should use the appropriate integer.
+	 */
+	if (on & (PM_LEFT|PM_RIGHT_B|PM_RIGHT_Z|PM_INTEGER))
+	    pm->ct = auxlen;
+	else
+	    pm->ct = 0;
+	pm->env = NULL;
+    } else {
+	/*
+	 * Create a new node for a parameter with the flags in `on' minus the
+	 * readonly flag
+	 */
+	pm = createparam(pname, on & ~PM_READONLY);
+	DPUTS(!pm, "BUG: parameter not created");
+	pm->ct = auxlen;
+    }
 
     if (altpm && PM_TYPE(pm->flags) == PM_SCALAR) {
 	/*
@@ -1618,6 +1699,28 @@
 	pm->level = locallevel;
     if (value && !(pm->flags & (PM_ARRAY|PM_HASHED)))
 	setsparam(pname, ztrdup(value));
+    else if (newspecial && !(pm->old->flags & PM_NORESTORE)) {
+	/*
+	 * We need to use the special setting function to re-initialise
+	 * the special parameter to empty.
+	 */
+	HEAPALLOC {
+	    switch (PM_TYPE(pm->flags)) {
+	    case PM_SCALAR:
+		pm->sets.cfn(pm, ztrdup(""));
+		break;
+	    case PM_INTEGER:
+		pm->sets.ifn(pm, 0);
+		break;
+	    case PM_ARRAY:
+		pm->sets.afn(pm, mkarray(NULL));
+		break;
+	    case PM_HASHED:
+		pm->sets.hfn(pm, newparamtable(17, pm->nam));
+		break;
+	    }
+	} LASTALLOC;
+    }
     pm->flags |= (on & PM_READONLY);
     if (value && (pm->flags & (PM_ARRAY|PM_HASHED))) {
 	zerrnam(cname, "%s: can't assign initial value for array", pname, 0);
--- Src/params.c.ns	Fri Jun 25 13:45:11 1999
+++ Src/params.c	Tue Jul  6 13:31:11 1999
@@ -2500,11 +2500,17 @@
     Param pm;
 
     MUSTUSEHEAP("arrfixenv");
+    pm = (Param) paramtab->getnode(paramtab, s);
+    /*
+     * Only one level of a parameter can be exported.  Unless
+     * ALLEXPORT is set, this must be global.
+     */
     if (t == path)
 	cmdnamtab->emptytable(cmdnamtab);
+    if (isset(ALLEXPORT) ? !!pm->old : pm->level)
+	return;
     u = t ? zjoin(t, ':') : "";
     len_s = strlen(s);
-    pm = (Param) paramtab->getnode(paramtab, s);
     for (ep = environ; *ep; ep++)
 	if (!strncmp(*ep, s, len_s) && (*ep)[len_s] == '=') {
 	    pm->env = replenv(*ep, u);
@@ -2685,8 +2691,46 @@
 scanendscope(HashNode hn, int flags)
 {
     Param pm = (Param)hn;
-    if(pm->level > locallevel)
-	unsetparam_pm(pm, 0, 0);
+    if (pm->level > locallevel) {
+	if ((pm->flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) {
+	    /*
+	     * Removable specials are normal in that they can be removed
+	     * to reveal an ordinary parameter beneath.  Here we handle
+	     * non-removable specials, which were made local by stealth
+	     * (see newspecial code in typeset_single()).  In fact the
+	     * visible pm is always the same struct; the pm->old is
+	     * just a place holder for old data and flags.
+	     */
+	    Param tpm = pm->old;
+
+	    DPUTS(!tpm || PM_TYPE(pm->flags) != PM_TYPE(tpm->flags) ||
+		  !(tpm->flags & PM_SPECIAL),
+		  "BUG: in restoring scope of special parameter");
+	    pm->old = tpm->old;
+	    pm->flags = (tpm->flags & ~PM_NORESTORE);
+	    pm->level = tpm->level;
+	    pm->ct = tpm->ct;
+	    pm->env = tpm->env;
+
+	    if (!(tpm->flags & PM_NORESTORE))
+		switch (PM_TYPE(pm->flags)) {
+		case PM_SCALAR:
+		    pm->sets.cfn(pm, tpm->u.str);
+		    break;
+		case PM_INTEGER:
+		    pm->sets.ifn(pm, tpm->u.val);
+		    break;
+		case PM_ARRAY:
+		    pm->sets.afn(pm, tpm->u.arr);
+		    break;
+		case PM_HASHED:
+		    pm->sets.hfn(pm, tpm->u.hash);
+		    break;
+		}
+	    zfree(tpm, sizeof(*tpm));
+	} else
+	    unsetparam_pm(pm, 0, 0);
+    }
 }
 
 
--- Src/zsh.h.ns	Tue Jul  6 09:25:44 1999
+++ Src/zsh.h	Tue Jul  6 14:08:46 1999
@@ -951,40 +951,41 @@
 /* flags for parameters */
 
 /* parameter types */
-#define PM_SCALAR	0	/* scalar                                     */
-#define PM_ARRAY	(1<<0)	/* array                                      */
-#define PM_INTEGER	(1<<1)	/* integer                                    */
-#define PM_HASHED	(1<<2)	/* association                                */
+#define PM_SCALAR	0	/* scalar                                   */
+#define PM_ARRAY	(1<<0)	/* array                                    */
+#define PM_INTEGER	(1<<1)	/* integer                                  */
+#define PM_HASHED	(1<<2)	/* association                              */
 
 #define PM_TYPE(X) (X & (PM_SCALAR|PM_INTEGER|PM_ARRAY|PM_HASHED))
 
-#define PM_LEFT		(1<<3)	/* left justify and remove leading blanks     */
-#define PM_RIGHT_B	(1<<4)	/* right justify and fill with leading blanks */
-#define PM_RIGHT_Z	(1<<5)	/* right justify and fill with leading zeros  */
-#define PM_LOWER	(1<<6)	/* all lower case                             */
+#define PM_LEFT		(1<<3)	/* left justify, remove leading blanks      */
+#define PM_RIGHT_B	(1<<4)	/* right justify, fill with leading blanks  */
+#define PM_RIGHT_Z	(1<<5)	/* right justify, fill with leading zeros   */
+#define PM_LOWER	(1<<6)	/* all lower case                           */
 
 /* The following are the same since they *
  * both represent -u option to typeset   */
-#define PM_UPPER	(1<<7)	/* all upper case                             */
-#define PM_UNDEFINED	(1<<7)	/* undefined (autoloaded) shell function      */
+#define PM_UPPER	(1<<7)	/* all upper case                           */
+#define PM_UNDEFINED	(1<<7)	/* undefined (autoloaded) shell function    */
 
-#define PM_READONLY	(1<<8)	/* readonly                                   */
-#define PM_TAGGED	(1<<9)	/* tagged                                     */
-#define PM_EXPORTED	(1<<10)	/* exported                                   */
+#define PM_READONLY	(1<<8)	/* readonly                                 */
+#define PM_TAGGED	(1<<9)	/* tagged                                   */
+#define PM_EXPORTED	(1<<10)	/* exported                                 */
 
 /* The following are the same since they *
  * both represent -U option to typeset   */
-#define PM_UNIQUE	(1<<11)	/* remove duplicates                          */
-#define PM_UNALIASED	(1<<11)	/* do not expand aliases when autoloading     */
+#define PM_UNIQUE	(1<<11)	/* remove duplicates                        */
+#define PM_UNALIASED	(1<<11)	/* do not expand aliases when autoloading   */
 
-#define PM_TIED 	(1<<12)	/* array tied to colon-path or v.v. */
-#define PM_LOCAL	(1<<13) /* this parameter will be made local */
-#define PM_SPECIAL	(1<<14) /* special builtin parameter                  */
-#define PM_DONTIMPORT	(1<<15)	/* do not import this variable                */
-#define PM_RESTRICTED	(1<<16) /* cannot be changed in restricted mode       */
-#define PM_UNSET	(1<<17)	/* has null value                             */
-#define PM_REMOVABLE	(1<<18)	/* special can be removed from paramtab */
-#define PM_AUTOLOAD     (1<<19) /* autoloaded from module */
+#define PM_TIED 	(1<<12)	/* array tied to colon-path or v.v.         */
+#define PM_LOCAL	(1<<13) /* this parameter will be made local        */
+#define PM_SPECIAL	(1<<14) /* special builtin parameter                */
+#define PM_DONTIMPORT	(1<<15)	/* do not import this variable              */
+#define PM_RESTRICTED	(1<<16) /* cannot be changed in restricted mode     */
+#define PM_UNSET	(1<<17)	/* has null value                           */
+#define PM_REMOVABLE	(1<<18)	/* special can be removed from paramtab     */
+#define PM_AUTOLOAD	(1<<19) /* autoloaded from module                   */
+#define PM_NORESTORE	(1<<20)	/* do not restore value of local special    */
 
 /* Flags for extracting elements of arrays and associative arrays */
 #define SCANPM_WANTVALS   (1<<0)

-- 
Peter Stephenson <pws@xxxxxxxxxxxxxxxxx>       Tel: +39 050 844536
WWW:  http://www.ifh.de/~pws/
Dipartimento di Fisica, Via Buonarroti 2, 56127 Pisa, Italy



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