From d3d5325d27db3a56b7caf5313617d5e84aae8fe7 Mon Sep 17 00:00:00 2001 From: "Barton E. Schaefer" Date: Sun, 8 Nov 2015 16:19:37 -0800 Subject: 37081: new module zsh/param/private for private-scoped parameters in functions --- ChangeLog | 5 + Doc/Makefile.in | 4 +- Doc/Zsh/mod_private.yo | 89 +++++++ Src/Modules/param_private.c | 587 ++++++++++++++++++++++++++++++++++++++++++ Src/Modules/param_private.mdd | 7 + Test/V10private.ztst | 265 +++++++++++++++++++ 6 files changed, 955 insertions(+), 2 deletions(-) create mode 100644 Doc/Zsh/mod_private.yo create mode 100644 Src/Modules/param_private.c create mode 100644 Src/Modules/param_private.mdd create mode 100644 Test/V10private.ztst diff --git a/ChangeLog b/ChangeLog index 110f10743..622b0dbd8 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ 2015-11-08 Barton E. Schaefer + * 37081: Doc/Makefile.in, Doc/Zsh/mod_private.yo, + Src/Modules/param_private.c, Src/Modules/param_private.mdd, + Test/V10private.ztst: new module zsh/param/private for + private-scoped parameters in functions + * 37080: Src/builtin.c, Src/params.c: use paramtab abstraction more consistently, add explanatory comments diff --git a/Doc/Makefile.in b/Doc/Makefile.in index 7645f42f7..d5899917e 100644 --- a/Doc/Makefile.in +++ b/Doc/Makefile.in @@ -62,8 +62,8 @@ Zsh/mod_computil.yo Zsh/mod_curses.yo \ Zsh/mod_datetime.yo Zsh/mod_db_gdbm.yo Zsh/mod_deltochar.yo \ Zsh/mod_example.yo Zsh/mod_files.yo Zsh/mod_langinfo.yo \ Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo Zsh/mod_newuser.yo \ -Zsh/mod_parameter.yo Zsh/mod_pcre.yo Zsh/mod_regex.yo \ -Zsh/mod_sched.yo Zsh/mod_socket.yo \ +Zsh/mod_parameter.yo Zsh/mod_pcre.yo Zsh/mod_private.yo \ +Zsh/mod_regex.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \ Zsh/mod_stat.yo Zsh/mod_system.yo Zsh/mod_tcp.yo \ Zsh/mod_termcap.yo Zsh/mod_terminfo.yo \ Zsh/mod_zftp.yo Zsh/mod_zle.yo Zsh/mod_zleparameter.yo \ diff --git a/Doc/Zsh/mod_private.yo b/Doc/Zsh/mod_private.yo new file mode 100644 index 000000000..c08da6791 --- /dev/null +++ b/Doc/Zsh/mod_private.yo @@ -0,0 +1,89 @@ +COMMENT(!MOD!zsh/param/private +Builtins for managing private-scoped parameters in function context. +!MOD!) +The tt(zsh/param/private) module is used to create parameters whose scope +is limited to the current function body, and em(not) to other functions +called by the current function. + +This module provides a single autoloaded builtin: +ifnzman() +startitem() +findex(private) +cindex(private parameter, creating) +item(tt(private) [ {tt(PLUS())|tt(-)}tt(AHUahlprtux) ] \ +[ {tt(PLUS())|tt(-)}tt(EFLRZi) [ var(n) ] ] [ var(name)[tt(=)var(value)] ... ])( +The tt(private) builtin accepts all the same options and arguments as tt(local) +(ifzman(zmanref(zshbuiltins))ifnzman(noderef(Shell Builtin Commands))) except +for the `tt(-)tt(T)' option. Tied parameters may not be made private. + +If used at the top level (outside a function scope), tt(private) creates a +normal parameter in the same manner as tt(declare) or tt(typeset). A +warning about this is printed if tt(WARN_CREATE_GLOBAL) is set +(ifzman(zmanref(zshoptions))ifnzman(noderef(Options))). Used inside a +function scope, tt(private) creates a local parameter similar to one +declared with tt(local), except having special properties noted below. + +Special parameters which expose or manipulate internal shell state, such +as tt(ARGC), tt(argv), tt(COLUMNS), tt(LINES), tt(UID), tt(EUID), tt(IFS), +tt(PROMPT), tt(RANDOM), tt(SECONDS), etc., cannot be made private unless +the `tt(-)tt(h)' option is used to hide the special meaning of the +parameter. This may change in the future. +) +enditem() + +As with other tt(typeset) equivalents, tt(private) is both a builtin and a +reserved word, so arrays may be assigned with parenthesized word list +var(name)tt(=LPAR())var(value)...tt(RPAR()) syntax. However, the reserved +word `tt(private)' is not available until tt(zsh/param/private) is loaded, +so care must be taken with order of execution and parsing for function +definitions which use tt(private). To compensate for this, the module +also adds the option `tt(-P)' to the `tt(local)' builtin to declare private +parameters. + +For example, this construction fails if tt(zsh/param/private) has not yet +been loaded when `tt(failing)' is defined: +example(bad_declaration+LPAR()RPAR() { + zmodload zsh/param/private + private array=LPAR() one two three RPAR() +}) + +This construction works because tt(local) is already a keyword, and the +module is loaded before the statement is executed: +example(good_declaration+LPAR()RPAR() { + zmodload zsh/param/private + local -P array=LPAR() one two three RPAR() +}) + +The following is usable in scripts but may have trouble with tt(autoload): +example(zmodload zsh/param/private +iffy_declaration+LPAR()RPAR() { + private array=LPAR() one two three RPAR() +}) + +The tt(private) builtin may always be used with scalar assignments and +for declarations without assignments. + +Parameters declared with tt(private) have the following properties: +ifnzman() +startitemize() +itemiz(Within the function body where it is declared, the parameter +behaves as a local, except as noted above for tied or special parameters.) +itemiz(The type of a parameter declared private cannot be changed in the +scope where it was declared, even if the parameter is unset. Thus an +array cannot be assigned to a private scalar, etc.) +itemiz(Within any other function called by the declaring function, the +private parameter does em(NOT) hide other parameters of the same name, so +for example a global parameter of the same name is visible and may be +assigned or unset. This includes calls to anonymous functions, although +that may also change in the future.) +itemiz(An exported private remains in the environment of inner scopes but +appears unset for the current shell in those scopes. Generally, exporting +private parameters should be avoided.) +enditemize() + +Note that this differs from the static scope defined by compiled languages +derived from C, in that the a new call to the same function creates a new +scope, i.e., the parameter is still associated with the call stack rather +than with the function definition. It differs from ksh `tt(typeset -S)' +because the syntax used to define the function has no bearing on whether +the parameter scope is respected. diff --git a/Src/Modules/param_private.c b/Src/Modules/param_private.c new file mode 100644 index 000000000..7f9aa7921 --- /dev/null +++ b/Src/Modules/param_private.c @@ -0,0 +1,587 @@ +/* + * param_private.c - bindings for private parameter scopes + * + * This file is part of zsh, the Z shell. + * + * Copyright (c) 2015 Barton E. Schaefer + * All rights reserved. + * + * Permission is hereby granted, without written agreement and without + * license or royalty fees, to use, copy, modify, and distribute this + * software and to distribute modified versions of this software for any + * purpose, provided that the above copyright notice and the following + * two paragraphs appear in all copies of this software. + * + * In no event shall Barton E. Schaefer or the Zsh Development + * Group be liable to any party for direct, indirect, special, incidental, or + * consequential damages arising out of the use of this software and its + * documentation, even if Barton E. Schaefer and the Zsh + * Development Group have been advised of the possibility of such damage. + * + * Barton E. Schaefer and the Zsh Development Group + * specifically disclaim any warranties, including, but not limited to, the + * implied warranties of merchantability and fitness for a particular purpose. + * The software provided hereunder is on an "as is" basis, and + * Barton E. Schaefer and the Zsh Development Group have no + * obligation to provide maintenance, support, updates, enhancements, or + * modifications. + * + */ + +#include "param_private.mdh" +#include "param_private.pro" + +struct gsu_closure { + union { + struct gsu_scalar s; + struct gsu_integer i; + struct gsu_float f; + struct gsu_array a; + struct gsu_hash h; + } u; + void *g; +}; + +const struct gsu_scalar scalar_private_gsu = +{ pps_getfn, pps_setfn, pps_unsetfn }; + +const struct gsu_integer integer_private_gsu = +{ ppi_getfn, ppi_setfn, ppi_unsetfn }; + +const struct gsu_float float_private_gsu = +{ ppf_getfn, ppf_setfn, ppf_unsetfn }; + +const struct gsu_array array_private_gsu = +{ ppa_getfn, ppa_setfn, ppa_unsetfn }; + +const struct gsu_hash hash_private_gsu = +{ pph_getfn, pph_setfn, pph_unsetfn }; + +/* + * The trick here is: + * + * bin_private() opens a new parameter scope, then calls bin_typeset(). + * + * bin_typeset() handles the usual parameter creation and error checks. + * + * makeprivate() then finds all parameters created in the new scope and + * rejects them if they can't be "promoted" to the surrounding scope. + * Otherwise it swaps out their GSU structure and promotes them so they + * will be removed when the surrounding scope ends. + * + * bin_private() then ends the current scope, which discards any of the + * parameters rejected by makeprivate(). + * + */ + +static int makeprivate_error = 0; + +static void +makeprivate(HashNode hn, UNUSED(int flags)) +{ + Param pm = (Param)hn; + if (pm->level == locallevel) { + if (pm->ename || (pm->node.flags & PM_NORESTORE) || + (pm->old && + (pm->old->level == locallevel - 1 || + ((pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL && + /* typeset_single() line 2300 discards PM_REMOVABLE -- why? */ + !is_private(pm->old))))) { + zwarnnam("private", "can't change scope of existing param: %s", + pm->node.nam); + makeprivate_error = 1; + return; + } + struct gsu_closure *gsu = zhalloc(sizeof(struct gsu_closure)); + switch (PM_TYPE(pm->node.flags)) { + case PM_SCALAR: + gsu->g = (void *)(pm->gsu.s); + gsu->u.s = scalar_private_gsu; + pm->gsu.s = (GsuScalar)gsu; + break; + case PM_INTEGER: + gsu->g = (void *)(pm->gsu.i); + gsu->u.i = integer_private_gsu; + pm->gsu.i = (GsuInteger)gsu; + break; + case PM_EFLOAT: + case PM_FFLOAT: + gsu->g = (void *)(pm->gsu.f); + gsu->u.f = float_private_gsu; + pm->gsu.f = (GsuFloat)gsu; + break; + case PM_ARRAY: + gsu->g = (void *)(pm->gsu.a); + gsu->u.a = array_private_gsu; + pm->gsu.a = (GsuArray)gsu; + break; + case PM_HASHED: + gsu->g = (void *)(pm->gsu.h); + gsu->u.h = hash_private_gsu; + pm->gsu.h = (GsuHash)gsu; + break; + default: + makeprivate_error = 1; + break; + } + /* PM_HIDE so new parameters in deeper scopes do not shadow */ + pm->node.flags |= (PM_HIDE|PM_SPECIAL|PM_REMOVABLE); + pm->level -= 1; + } +} + +/**/ +static int +is_private(Param pm) +{ + switch (PM_TYPE(pm->node.flags)) { + case PM_SCALAR: + if (!pm->gsu.s || pm->gsu.s->unsetfn != pps_unsetfn) + return 0; + break; + case PM_INTEGER: + if (!pm->gsu.i || pm->gsu.i->unsetfn != ppi_unsetfn) + return 0; + break; + case PM_EFLOAT: + case PM_FFLOAT: + if (!pm->gsu.f || pm->gsu.f->unsetfn != ppf_unsetfn) + return 0; + break; + case PM_ARRAY: + if (!pm->gsu.a || pm->gsu.a->unsetfn != ppa_unsetfn) + return 0; + break; + case PM_HASHED: + if (!pm->gsu.h || pm->gsu.h->unsetfn != pph_unsetfn) + return 0; + break; + default: + /* error */ + return 0; + } + return 1; +} + +static int fakelevel; + +/**/ +static int +bin_private(char *nam, char **args, LinkList assigns, Options ops, int func) +{ + int from_typeset = 1; + makeprivate_error = 0; + + if (!OPT_ISSET(ops, 'P')) + return bin_typeset(nam, args, assigns, ops, func); + else if (OPT_ISSET(ops, 'T')) { + zwarn("bad option: -T"); + return 1; + } + + if (locallevel == 0) { + if (isset(WARNCREATEGLOBAL)) + zwarnnam(nam, "invalid local scope, using globals"); + return bin_typeset("private", args, assigns, ops, func); + } + + ops->ind['g'] = 2; /* force bin_typeset() to behave as "local" */ + + queue_signals(); + fakelevel = locallevel; + startparamscope(); + from_typeset = bin_typeset("private", args, assigns, ops, func); + scanhashtable(paramtab, 0, 0, 0, makeprivate, 0); + endparamscope(); + fakelevel = 0; + unqueue_signals(); + + return makeprivate_error | from_typeset; +} + +static void +setfn_error(Param pm) +{ + pm->node.flags |= PM_UNSET; + zerr("%s: attempt to assign private in nested scope", pm->node.nam); +} + +/* + * How the GSU functions work: + * + * The getfn and setfn family compare to locallevel and then call through + * to the original getfn or setfn. This means you can't assign at a + * deeper scope to any parameter declared private unless you first declare + * it local again at the new scope. Testing locallevel in getfn is most + * likely unnecessary given the scopeprivate() wrapper installed below. + * + * The unsetfn family compare locallevel and restore the old GSU before + * calling the original unsetfn. This assures that if the old unsetfn + * wants to use its getfn or setfn, they're unconditionally present. + * + */ + +/**/ +static char * +pps_getfn(Param pm) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.s); + GsuScalar gsu = (GsuScalar)(c->g); + + if (locallevel >= pm->level) + return gsu->getfn(pm); + else + return (char *) hcalloc(1); +} + +/**/ +static void +pps_setfn(Param pm, char *x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.s); + GsuScalar gsu = (GsuScalar)(c->g); + if (locallevel == pm->level) + gsu->setfn(pm, x); + else + setfn_error(pm); +} + +/**/ +static void +pps_unsetfn(Param pm, int x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.s); + GsuScalar gsu = (GsuScalar)(c->g); + pm->gsu.s = gsu; + if (locallevel <= pm->level) + gsu->unsetfn(pm, x); +} + +/**/ +static zlong +ppi_getfn(Param pm) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.i); + GsuInteger gsu = (GsuInteger)(c->g); + if (locallevel >= pm->level) + return gsu->getfn(pm); + else + return 0; +} + +/**/ +static void +ppi_setfn(Param pm, zlong x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.i); + GsuInteger gsu = (GsuInteger)(c->g); + if (locallevel == pm->level) + gsu->setfn(pm, x); + else + setfn_error(pm); +} + +/**/ +static void +ppi_unsetfn(Param pm, int x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.i); + GsuInteger gsu = (GsuInteger)(c->g); + pm->gsu.i = gsu; + if (locallevel <= pm->level) + gsu->unsetfn(pm, x); +} + +/**/ +static double +ppf_getfn(Param pm) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.f); + GsuFloat gsu = (GsuFloat)(c->g); + if (locallevel >= pm->level) + return gsu->getfn(pm); + else + return 0; +} + +/**/ +static void +ppf_setfn(Param pm, double x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.f); + GsuFloat gsu = (GsuFloat)(c->g); + if (locallevel == pm->level) + gsu->setfn(pm, x); + else + setfn_error(pm); +} + +/**/ +static void +ppf_unsetfn(Param pm, int x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.f); + GsuFloat gsu = (GsuFloat)(c->g); + pm->gsu.f = gsu; + if (locallevel <= pm->level) + gsu->unsetfn(pm, x); +} + +/**/ +static char ** +ppa_getfn(Param pm) +{ + static char *nullarray = NULL; + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.a); + GsuArray gsu = (GsuArray)(c->g); + if (locallevel >= pm->level) + return gsu->getfn(pm); + else + return &nullarray; +} + +/**/ +static void +ppa_setfn(Param pm, char **x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.a); + GsuArray gsu = (GsuArray)(c->g); + if (locallevel == pm->level) + gsu->setfn(pm, x); + else + setfn_error(pm); +} + +/**/ +static void +ppa_unsetfn(Param pm, int x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.a); + GsuArray gsu = (GsuArray)(c->g); + pm->gsu.a = gsu; + if (locallevel <= pm->level) + gsu->unsetfn(pm, x); +} + +static HashTable emptytable; + +/**/ +static HashTable +pph_getfn(Param pm) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.h); + GsuHash gsu = (GsuHash)(c->g); + if (locallevel >= pm->level) + return gsu->getfn(pm); + else + return emptytable; +} + +/**/ +static void +pph_setfn(Param pm, HashTable x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.h); + GsuHash gsu = (GsuHash)(c->g); + if (locallevel == pm->level) + gsu->setfn(pm, x); + else + setfn_error(pm); +} + +/**/ +static void +pph_unsetfn(Param pm, int x) +{ + struct gsu_closure *c = (struct gsu_closure *)(pm->gsu.h); + GsuHash gsu = (GsuHash)(c->g); + pm->gsu.h = gsu; + if (locallevel <= pm->level) + gsu->unsetfn(pm, x); +} + +/* + * How visibility works: + * + * Upon entering a new function scope, we find all the private parameters + * at this locallevel. Any that we find are marked PM_UNSET. If they are + * already unset, they are also marked as PM_NORESTORE. + * + * On exit from the scope, we find the same parameters again and remove + * the PM_UNSET and PM_NORESTORE flags as appropriate. We're guaraneed + * by makeprivate() that PM_NORESTORE won't conflict with anything here. + * + */ + +static void +scopeprivate(HashNode hn, int onoff) +{ + Param pm = (Param)hn; + if (pm->level == locallevel) { + if (!is_private(pm)) + return; + if (onoff == PM_UNSET) + if (pm->node.flags & PM_UNSET) + pm->node.flags |= PM_NORESTORE; + else + pm->node.flags |= PM_UNSET; + else if (!(pm->node.flags & PM_NORESTORE)) + pm->node.flags &= ~PM_UNSET; + pm->node.flags &= ~PM_NORESTORE; + } +} + +static struct funcwrap wrapper[] = { + WRAPDEF(wrap_private) +}; + +/**/ +static int +wrap_private(Eprog prog, FuncWrap w, char *name) +{ + static int wraplevel = 0; + + if (wraplevel < locallevel /* && strcmp(name, "(anon)") != 0 */) { + int owl = wraplevel; + wraplevel = locallevel; + scanhashtable(paramtab, 0, 0, 0, scopeprivate, PM_UNSET); + runshfunc(prog, w, name); + scanhashtable(paramtab, 0, 0, 0, scopeprivate, 0); + wraplevel = owl; + return 0; + } + return 1; +} + +static HashNode (*getparamnode) _((HashTable, const char *)); + +/**/ +static HashNode +getprivatenode(HashTable ht, const char *nam) +{ + HashNode hn = getparamnode(ht, nam); + Param pm = (Param) hn; + + while (!fakelevel && pm && locallevel > pm->level && is_private(pm)) + pm = pm->old; + return (HashNode)pm; +} + +/**/ +static HashNode +getprivatenode2(HashTable ht, const char *nam) +{ + /* getparamnode() would follow autoloads, we must not do that here */ + HashNode hn = gethashnode2(ht, nam); + Param pm = (Param) hn; + + while (!fakelevel && pm && locallevel > pm->level && is_private(pm)) + pm = pm->old; + return (HashNode)pm; +} + +/**/ +static void +printprivatenode(HashNode hn, int printflags) +{ + Param pm = (Param) hn; + while (pm && (!fakelevel || + (fakelevel > pm->level && (pm->node.flags & PM_UNSET))) && + locallevel > pm->level && is_private(pm)) + pm = pm->old; + /* Ideally, we'd print the word "private" here instead of "typeset" + * when the parameter is in fact a private, but that would require + * re-implementing the entirety of printparamnode(). */ + if (pm) + printparamnode((HashNode)pm, printflags); +} + +/* + * Standard module configuration/linkage + */ + +static struct builtin bintab[] = { + /* Copied from BUILTIN("local"), "P" added */ + BUILTIN("private", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_private, 0, -1, 0, "AE:%F:%HL:%PR:%TUZ:%ahi:%lprtux", "P") +}; + +static struct features module_features = { + bintab, sizeof(bintab)/sizeof(*bintab), + NULL, 0, + NULL, 0, + NULL, 0, + 0 +}; + +static struct builtin save_local; +static struct reswd reswd_private = {{NULL, "private", 0}, TYPESET}; + +/**/ +int +setup_(UNUSED(Module m)) +{ + HashNode hn = builtintab->getnode(builtintab, "local"); + + /* Horrible, horrible hack */ + getparamnode = realparamtab->getnode; + realparamtab->getnode = getprivatenode; + realparamtab->getnode2 = getprivatenode2; + realparamtab->printnode = printprivatenode; + + /* Even more horrible hack */ + save_local = *(Builtin)hn; + ((Builtin)hn)->handlerfunc = bintab[0].handlerfunc; + ((Builtin)hn)->optstr = bintab[0].optstr; + + reswdtab->addnode(reswdtab, reswd_private.node.nam, &reswd_private); + + return 0; +} + +/**/ +int +features_(Module m, char ***features) +{ + *features = featuresarray(m, &module_features); + return 0; +} + +/**/ +int +enables_(Module m, int **enables) +{ + return handlefeatures(m, &module_features, enables); +} + +/**/ +int +boot_(Module m) +{ + emptytable = newparamtable(1, "private"); + return addwrapper(m, wrapper); +} + +/**/ +int +cleanup_(Module m) +{ + HashNode hn = builtintab->getnode(builtintab, "local"); + *(Builtin)hn = save_local; + + removehashnode(reswdtab, "private"); + + realparamtab->getnode = getparamnode; + realparamtab->getnode2 = gethashnode2; + realparamtab->printnode = printparamnode; + + deletewrapper(m, wrapper); + return setfeatureenables(m, &module_features, NULL); +} + +/**/ +int +finish_(UNUSED(Module m)) +{ + deletehashtable(emptytable); + return 0; +} diff --git a/Src/Modules/param_private.mdd b/Src/Modules/param_private.mdd new file mode 100644 index 000000000..e6eb3228f --- /dev/null +++ b/Src/Modules/param_private.mdd @@ -0,0 +1,7 @@ +name=zsh/param/private +link=dynamic +load=yes + +autofeatures="b:private" + +objects="param_private.o" diff --git a/Test/V10private.ztst b/Test/V10private.ztst new file mode 100644 index 000000000..444b5b4ea --- /dev/null +++ b/Test/V10private.ztst @@ -0,0 +1,265 @@ +# Tests for the zsh/param/private module + +%prep + + zmodload zsh/param/private + +%test + + (zmodload -u zsh/param/private && zmodload zsh/param/private) +0:unload and reload the module without crashing + + typeset scalar_test=toplevel + () { + print $scalar_test + private scalar_test + print $+scalar_test + unset scalar_test + print $+scalar_test + } + print $scalar_test +0:basic scope hiding +>toplevel +>1 +>0 +>toplevel + + typeset scalar_test=toplevel + print $scalar_test + () { + private scalar_test=function + print $scalar_test + } + print $scalar_test +0:enter and exit a scope +>toplevel +>function +>toplevel + + print $+unset_test + () { + private unset_test + print $+unset_test + unset_test=setme + print $unset_test + } + print $+unset_test +0:variable defined only in scope +>0 +>1 +>setme +>0 + + # Depends on zsh-5.0.9 typeset keyword + typeset -a array_test=(top level) + () { + local -Pa array_test=(in function) + () { + private array_test + print $+array_test + } + print $array_test + } + print $array_test +0:nested scope with different type, correctly restored +>1 +>in function +>top level + + typeset -a array_test=(top level) + () { + private array_test + array_test=(in function) + } +1:type of private may not be changed by assignment +?(anon):2: array_test: attempt to assign array value to non-array + + typeset -A hash_test=(top level) + () { + setopt localoptions noglob + private hash_test[top] + } +1:associative array fields may not be private +?(anon):private:2: hash_test[top]: can't create local array elements + + () { + private path + } +1:tied params may not be private, part 1 +?(anon):private:1: can't change scope of existing param: path + + () { + private PATH + } +1:tied params may not be private, part 2 +?(anon):private:1: can't change scope of existing param: PATH + + () { + private -h path + print X$path + } +0:privates may hide tied paramters +>X + + # Deliberate type mismatch here + typeset -a hash_test=(top level) + typeset -p hash_test + inner () { + private -p hash_test + print ${(t)hash_test} ${(kv)hash_test} + } + outer () { + local -PA hash_test=(in function) + typeset -p hash_test + inner + } + outer + print ${(kv)hash_test} +0:private hides value from surrounding scope in nested scope +>typeset -a hash_test +>hash_test=( top level ) +>typeset -A hash_test +>hash_test=( in function ) +>typeset -a hash_test +>hash_test=( top level ) +>array-local top level +>top level +F:note "typeset" rather than "private" in output from outer + + () { + private -a array_test + local array_test=scalar + } +1:private cannot be re-declared as local +?(anon):local:2: array_test: inconsistent type for assignment + + () { + local hash_test=scalar + private -A hash_test + } +1:local cannot be re-declared as private +?(anon):private:2: can't change scope of existing param: hash_test + + inner () { + print $+scalar_test + $ZTST_testdir/../Src/zsh -fc 'print X $scalar_test' + } + () { + private -x scalar_test=whaat + $ZTST_testdir/../Src/zsh -fc 'print X $scalar_test' + inner + print Y $scalar_test + } +0:exported private behaves like a local, part 1 +>X whaat +>0 +>X whaat +>Y whaat + + inner () { + typeset -p array_test + $ZTST_testdir/../Src/zsh -fc 'print X $array_test' + } + () { + local -Pax array_test=(whaat) + print Y $array_test + $ZTST_testdir/../Src/zsh -fc 'print X $array_test' + inner + } +0:exported private behaves like a local, part 2 (arrays do not export) +?inner:typeset:1: no such variable: array_test +>Y whaat +>X +>X + + inner () { + print $+scalar_test + $ZTST_testdir/../Src/zsh -fc 'print X $scalar_test' + } + () { + private scalar_test=whaat + export scalar_test + $ZTST_testdir/../Src/zsh -fc 'print X $scalar_test' + inner + () { + print $+scalar_test + $ZTST_testdir/../Src/zsh -fc 'print X $scalar_test' + } + print Y $scalar_test + } +0:exported private behaves like a local, part 3 (export does not change scope) +>X whaat +>0 +>X whaat +>0 +>X whaat +>Y whaat + + typeset -A hash_test=(top level) + () { + local -PA hash_test=(in function) + () { + print X ${(kv)hash_test} + } + print Y ${(kv)hash_test} + } + print ${(kv)hash_test} +0:privates are not visible in anonymous functions, part 1 +>X top level +>Y in function +>top level + + typeset -A hash_test=(top level) + () { + local -PA hash_test=(in function) + () { + print X ${(kv)hash_test} + hash_test[in]=deeper + } + print Y ${(kv)hash_test} + } + print ${(okv)hash_test} +0:privates are not visible in anonymous functions, part 2 +>X top level +>Y in function +>deeper in level top + + typeset -A hash_test=(top level) + () { + local -Pa array_test=(in function) + local -PA hash_test=($array_test) + () { + print X ${(kv)hash_test} + hash_test=(even deeper) + array_test+=(${(kv)hash_test}) + } + print Y ${(kv)hash_test} Z $array_test + } + print ${(kv)hash_test} +0:privates are not visible in anonymous functions, part 3 +>X top level +>Y in function Z in function +>even deeper + + typeset -A hash_test=(top level) + () { + local -PA hash_test=(in function) + () { + print X ${(kv)hash_test} + unset hash_test + } + print Y ${(kv)hash_test} + } + print ${(t)hash_test} ${(kv)hash_test} +0:privates are not visible in anonymous functions, part 4 +>X top level +>Y in function +> + + # Subshell because otherwise this silently dumps core when broken + ( () { private SECONDS } ) +1:special parameters cannot be made private +?(anon):private: can't change scope of existing param: SECONDS + + () { private -h SECONDS } +0:private parameter may hide a special parameter -- cgit 1.4.1