mirror of
git://git.code.sf.net/p/zsh/code
synced 2025-01-01 05:16:05 +01:00
37081: new module zsh/param/private for private-scoped parameters in functions
This commit is contained in:
parent
30b90f166e
commit
d3d5325d27
6 changed files with 955 additions and 2 deletions
|
@ -1,5 +1,10 @@
|
|||
2015-11-08 Barton E. Schaefer <schaefer@brasslantern.com>
|
||||
|
||||
* 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
|
||||
|
||||
|
|
|
@ -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 \
|
||||
|
|
89
Doc/Zsh/mod_private.yo
Normal file
89
Doc/Zsh/mod_private.yo
Normal file
|
@ -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.
|
587
Src/Modules/param_private.c
Normal file
587
Src/Modules/param_private.c
Normal file
|
@ -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;
|
||||
}
|
7
Src/Modules/param_private.mdd
Normal file
7
Src/Modules/param_private.mdd
Normal file
|
@ -0,0 +1,7 @@
|
|||
name=zsh/param/private
|
||||
link=dynamic
|
||||
load=yes
|
||||
|
||||
autofeatures="b:private"
|
||||
|
||||
objects="param_private.o"
|
265
Test/V10private.ztst
Normal file
265
Test/V10private.ztst
Normal file
|
@ -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
|
Loading…
Reference in a new issue