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 --- Src/Modules/param_private.c | 587 ++++++++++++++++++++++++++++++++++++++++++ Src/Modules/param_private.mdd | 7 + 2 files changed, 594 insertions(+) create mode 100644 Src/Modules/param_private.c create mode 100644 Src/Modules/param_private.mdd (limited to 'Src') 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" -- cgit 1.4.1