about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--Doc/Makefile.in4
-rw-r--r--Doc/Zsh/mod_private.yo89
-rw-r--r--Src/Modules/param_private.c587
-rw-r--r--Src/Modules/param_private.mdd7
-rw-r--r--Test/V10private.ztst265
6 files changed, 955 insertions, 2 deletions
diff --git a/ChangeLog b/ChangeLog
index 110f10743..622b0dbd8 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -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
 
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