about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog25
-rw-r--r--Completion/Unix/Command/_chmod10
-rw-r--r--Completion/Unix/Command/_chown12
-rw-r--r--Completion/Unix/Command/_cvs9
-rw-r--r--Completion/Unix/Type/_list_files15
-rw-r--r--Completion/Zsh/Command/_stat2
-rw-r--r--Completion/Zsh/Command/_zstyle1
-rw-r--r--Completion/Zsh/Type/_file_descriptors4
-rw-r--r--Doc/Zsh/builtins.yo64
-rw-r--r--Doc/Zsh/compsys.yo7
-rw-r--r--Doc/Zsh/mod_stat.yo17
-rw-r--r--Etc/zsh-development-guide274
-rw-r--r--Src/Builtins/rlimits.c27
-rw-r--r--Src/Builtins/sched.c28
-rw-r--r--Src/Modules/cap.c30
-rw-r--r--Src/Modules/clone.c30
-rw-r--r--Src/Modules/datetime.c41
-rw-r--r--Src/Modules/example.c35
-rw-r--r--Src/Modules/files.c28
-rw-r--r--Src/Modules/langinfo.c92
-rw-r--r--Src/Modules/langinfo.mdd2
-rw-r--r--Src/Modules/mapfile.c99
-rw-r--r--Src/Modules/mathfunc.c28
-rw-r--r--Src/Modules/parameter.c251
-rw-r--r--Src/Modules/pcre.c41
-rw-r--r--Src/Modules/regex.c30
-rw-r--r--Src/Modules/socket.c29
-rw-r--r--Src/Modules/stat.c29
-rw-r--r--Src/Modules/system.c138
-rw-r--r--Src/Modules/tcp.c28
-rw-r--r--Src/Modules/termcap.c110
-rw-r--r--Src/Modules/terminfo.c112
-rw-r--r--Src/Modules/zftp.c60
-rw-r--r--Src/Modules/zprof.c29
-rw-r--r--Src/Modules/zpty.c30
-rw-r--r--Src/Modules/zselect.c32
-rw-r--r--Src/Modules/zutil.c27
-rw-r--r--Src/Zle/compctl.c28
-rw-r--r--Src/Zle/complete.c33
-rw-r--r--Src/Zle/complist.c27
-rw-r--r--Src/Zle/computil.c28
-rw-r--r--Src/Zle/deltochar.c29
-rw-r--r--Src/Zle/zle_main.c27
-rw-r--r--Src/Zle/zle_thingy.c2
-rw-r--r--Src/Zle/zle_tricky.c2
-rw-r--r--Src/Zle/zleparameter.c118
-rw-r--r--Src/builtin.c31
-rw-r--r--Src/cond.c5
-rw-r--r--Src/exec.c4
-rw-r--r--Src/init.c10
-rw-r--r--Src/mkbltnmlst.sh7
-rw-r--r--Src/mkmakemod.sh2
-rw-r--r--Src/modentry.c10
-rw-r--r--Src/module.c1983
-rw-r--r--Src/params.c45
-rw-r--r--Src/zsh.h91
-rw-r--r--Test/B02typeset.ztst2
-rw-r--r--Test/V01zmodload.ztst6
-rw-r--r--Test/V04features.ztst162
59 files changed, 2858 insertions, 1620 deletions
diff --git a/ChangeLog b/ChangeLog
index 12a464445..427d8ab7d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,30 @@
 2007-05-28  Peter Stephenson  <p.w.stephenson@ntlworld.com>
 
+	* Not posted but see 23479:Completion/Unix/Command/_chmod,
+	Completion/Unix/Command/_chown, Completion/Unix/Command/_cvs,
+	Completion/Unix/Type/_list_files, Completion/Zsh/Command/_stat,
+	Completion/Zsh/Command/_zstyle,
+	Completion/Zsh/Type/_file_descriptors,Doc/Zsh/builtins.yo,
+	Doc/Zsh/compsys.yo, Doc/Zsh/mod_stat.yo,
+	Etc/zsh-development-guide, Src/builtin.c, Src/cond.c, Src/exec.c
+	Src/init.c, Src/mkbltnmlst.sh, Src/mkmakemod.sh, Src/modentry.c,
+	Src/module.c, Src/params.c, Src/zsh.h, Src/Builtins/rlimits.c,
+	Src/Builtins/sched.c, Src/Modules/cap.c, Src/Modules/clone.c,
+	Src/Modules/datetime.c, Src/Modules/example.c,
+	Src/Modules/files.c, Src/Modules/langinfo.c,
+	Src/Modules/langinfo.mdd, Src/Modules/mapfile.c,
+	Src/Modules/mathfunc.c, Src/Modules/parameter.c,
+	Src/Modules/pcre.c, Src/Modules/regex.c, Src/Modules/socket.c,
+	Src/Modules/stat.c, Src/Modules/system.c, Src/Modules/tcp.c,
+	Src/Modules/termcap.c, Src/Modules/terminfo.c, Src/Modules/zftp.c,
+	Src/Modules/zprof.c, Src/Modules/zpty.c, Src/Modules/zselect.c,
+	Src/Modules/zutil.c, Src/Zle/compctl.c, Src/Zle/complete.c,
+	Src/Zle/complist.c, Src/Zle/computil.c, Src/Zle/deltochar.c,
+	Src/Zle/zle_main.c, Src/Zle/zle_thingy.c, Src/Zle/zle_tricky.c,
+	Src/Zle/zleparameter.c, Test/B02typeset.ztst,
+	Test/V01zmodload.ztst, Test/V04features.ztst: add "zmodload -F"
+	and internal features support for modules.
+
 	* 23478: Src/Zle/compresult.c: don't run reverse-menu-complete
 	if no completion yet.
 
diff --git a/Completion/Unix/Command/_chmod b/Completion/Unix/Command/_chmod
index 3df93fd64..84013d591 100644
--- a/Completion/Unix/Command/_chmod
+++ b/Completion/Unix/Command/_chmod
@@ -54,13 +54,9 @@ case "$state" in
   ;;
   files)
     if [[ -n $opt_args[--reference] ]]; then
-      if zstyle -t ":completion:${curcontext}:" disable-stat; then
-	_files && ret=0
-      else
-	zmodload -i zsh/stat 2>/dev/null
-	typeset -i8 ref=$(stat +mode $opt_args[--reference])
-	_wanted files expl file _files -g "*(-.^f${ref#??})" && ret=0
-      fi
+      zmodload -F zsh/stat zstat 2>/dev/null
+      typeset -i8 ref=$(zstat +mode $opt_args[--reference])
+      _wanted files expl file _files -g "*(-.^f${ref#??})" && ret=0
     elif [[ $words[2] = [0-7]## ]]; then
       _wanted files expl file _files -g "*(-.^f$words[2])" && ret=0
     else
diff --git a/Completion/Unix/Command/_chown b/Completion/Unix/Command/_chown
index 3371e8ea4..9246db42c 100644
--- a/Completion/Unix/Command/_chown
+++ b/Completion/Unix/Command/_chown
@@ -50,14 +50,10 @@ case $state in
   files)
     (( $+opt_args[-h] || $+opt_args[--no-dereference] )) || deref="-"
     if (( $+opt_args[--reference] )); then
-      if zstyle -t ":completion:${curcontext}:" disable-stat; then
-	_files && ret=0
-      else
-	zmodload -i zsh/stat 2>/dev/null
-	usr=$(stat +uid $opt_args[--reference])
-	grp=$(stat +gid $opt_args[--reference])
-	_wanted files expl file _files -g "*($deref^u$usr,$deref^g$grp)" && ret=0
-      fi
+      zmodload -F zsh/stat zstat 2>/dev/null
+      usr=$(zstat +uid $opt_args[--reference])
+      grp=$(zstat +gid $opt_args[--reference])
+      _wanted files expl file _files -g "*($deref^u$usr,$deref^g$grp)" && ret=0
       return ret
     fi
     if [[ $service = chgrp ]]; then
diff --git a/Completion/Unix/Command/_cvs b/Completion/Unix/Command/_cvs
index 839075f64..c5fd75050 100644
--- a/Completion/Unix/Command/_cvs
+++ b/Completion/Unix/Command/_cvs
@@ -550,11 +550,10 @@ _cvs_watchers() {
 
 (( $+functions[_cvs_loadstat] )) ||
 _cvs_loadstat() {
-  zstyle -t ":completion:${curcontext}:" disable-stat && return 1
   (( $+_cvs_loadstat_status )) && return $_cvs_loadstat_status
 
-  zmodload -i zsh/stat 2>/dev/null
-  (( _cvs_loadstat_status = ! $+builtins[stat] ))
+  zmodload -F zsh/stat zstat 2>/dev/null
+  (( _cvs_loadstat_status = ! $+builtins[zstat] ))
   return $_cvs_loadstat_status
 }
 
@@ -566,7 +565,7 @@ _cvs_root() {
 
   if [[ -f "${cvspassfile::=${CVS_PASSFILE:-$HOME/.cvspass}}" ]]; then
     if _cvs_loadstat; then
-      id="$(LC_ALL=C builtin stat -g +mtime -F '%Y/%m/%d-%T' "$cvspassfile")"
+      id="$(LC_ALL=C builtin zstat -g +mtime -F '%Y/%m/%d-%T' "$cvspassfile")"
     else
       id="$(LC_ALL=C ls -l "$cvspassfile")"
     fi
@@ -865,7 +864,7 @@ _cvs_modified_entries() {
     realdir=${(e)~linedir}
     [[ -f "$realdir"CVS/Entries ]] && {
       local -a mtime
-      LC_ALL=C builtin stat -A mtime -gn +mtime -F $'%a %b %e %T %Y\n' ${realdir}*(D) 2>/dev/null
+      LC_ALL=C builtin zstat -A mtime -gn +mtime -F $'%a %b %e %T %Y\n' ${realdir}*(D) 2>/dev/null
       [[ -n ${pat::="${(@j:|:)${(@)${(@)${(@)${(@)${(@)${(@M)${(@f)"$(<"$realdir"CVS/Entries)"}:#/*}#/}/${slash}[^${slash}]#${slash}//}%/[^/]#/[^/]#}:#${(j:|:)~${(f)${(j:/:)${mtime##*/}}//(#m)[][*?()<|^~#\\]/\\$MATCH}#/}}%%/*}//(#m)[][*?()<|^~#\\]/\\$MATCH}"} ]]
     } &&
     _wanted files expl 'modified file' _path_files -g "$pat"
diff --git a/Completion/Unix/Type/_list_files b/Completion/Unix/Type/_list_files
index 8475d2056..bcb42907d 100644
--- a/Completion/Unix/Type/_list_files
+++ b/Completion/Unix/Type/_list_files
@@ -46,15 +46,7 @@ done
 
 (( ok )) || return 1
 
-zmodload -i zsh/stat 2>/dev/null || return 1
-
-{
-# Enable stat temporarily if disabled to avoid clashes.
-integer disable_stat
-if [[ ${builtins[stat]} != defined ]]; then
-  (( disable_stat = 1 ))
-  enable stat
-fi
+zmodload -F zsh/stat zstat 2>/dev/null || return 1
 
 dir=${2:+$2/}
 dir=${(Q)dir}
@@ -66,7 +58,7 @@ for f in ${(PQ)1}; do
   fi
 
   # Borrowed from Functions/Example/zls
-  stat -s -H stat -F "%b %e %H:%M" - "$dir$f" >/dev/null 2>&1
+  zstat -s -H stat -F "%b %e %H:%M" - "$dir$f" >/dev/null 2>&1
 
   listfiles+=("$stat[mode] ${(l:3:)stat[nlink]} ${(r:8:)stat[uid]} \
  ${(r:8:)stat[gid]} ${(l:8:)stat[size]} $stat[mtime] $f")
@@ -74,7 +66,4 @@ done
 
 (( ${#listfiles} )) && listopts=(-d listfiles -l -o)
 
-} always {
-  (( disable_stat )) && disable stat
-}
 return 0
diff --git a/Completion/Zsh/Command/_stat b/Completion/Zsh/Command/_stat
index 5ba06388b..03e42e3af 100644
--- a/Completion/Zsh/Command/_stat
+++ b/Completion/Zsh/Command/_stat
@@ -1,4 +1,4 @@
-#compdef stat
+#compdef stat zstat
 
 local expl ret=1
 
diff --git a/Completion/Zsh/Command/_zstyle b/Completion/Zsh/Command/_zstyle
index 4090f0ac3..2bf2709ca 100644
--- a/Completion/Zsh/Command/_zstyle
+++ b/Completion/Zsh/Command/_zstyle
@@ -40,7 +40,6 @@ styles=(
   completer		 c:completer
   condition		 c:bool
   cursor		 c:cursor
-  disable-stat		 c:bool
   disabled               c:bool
   domains                c:
   expand		 c:
diff --git a/Completion/Zsh/Type/_file_descriptors b/Completion/Zsh/Type/_file_descriptors
index 6d21ed2b6..82d19d62a 100644
--- a/Completion/Zsh/Type/_file_descriptors
+++ b/Completion/Zsh/Type/_file_descriptors
@@ -7,9 +7,9 @@ for i in {0..9}; [[ -e /dev/fd/$i ]] && fds+=( $i )
 if zstyle -T ":completion:${curcontext}:" verbose && [[ -e /proc/$$/fd ]]; then
   zstyle -s ":completion:${curcontext}:" list-separator sep || sep=--
 
-  if zmodload -e zsh/stat; then
+  if zmodload -F zsh/stat zstat; then
     for i in "${fds[@]}"; do
-      stat +link -A link /proc/$$/fd/$i
+      zstat +link -A link /proc/$$/fd/$i
       list+=( "$i $sep ${link[1]}" )
     done
   elif (( $+commands[readlink] )); then
diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo
index 0f90bca6d..cd655c146 100644
--- a/Doc/Zsh/builtins.yo
+++ b/Doc/Zsh/builtins.yo
@@ -1845,6 +1845,7 @@ findex(zmodload)
 cindex(modules, loading)
 cindex(loading modules)
 xitem(tt(zmodload) [ tt(-dL) ] [ ... ])
+xitem(tt(zmodload -F) [ tt(-lLe) tt(-P) tt(param) ] var(module) [tt(PLUS()-)]var(feature...))
 xitem(tt(zmodload -e) [ tt(-A) ] [ ... ])
 xitem(tt(zmodload) [ tt(-a) [ tt(-bcpf) [ tt(-I) ] ] ] [ tt(-iL) ] ...)
 xitem(tt(zmodload) tt(-u) [ tt(-abcdpf) [ tt(-I) ] ] [ tt(-iL) ] ...)
@@ -1867,21 +1868,24 @@ item(tt(zmodload) tt(-u) [ tt(-i) ] var(name) ...)(
 In the simplest case, tt(zmodload) loads a binary module.  The module must
 be in a file with a name consisting of the specified var(name) followed by
 a standard suffix, usually `tt(.so)' (`tt(.sl)' on HPUX).
-If the module to be loaded is
-already loaded and the tt(-i) option is given, the duplicate module is
-ignored.  Otherwise tt(zmodload) prints an error message and returns
-a non-zero status.  If tt(zmodload) detects an inconsistency, such as an
+If the module to be loaded is already loaded the duplicate module is
+ignored.  If tt(zmodload) detects an inconsistency, such as an
 invalid module name or circular dependency list, the current code block is 
-aborted.  Hence `tt(zmodload -i) var(module) tt(2>/dev/null)' is sufficient
+aborted.   Hence `tt(zmodload) var(module) tt(2>/dev/null)' is sufficient
 to test whether a module is available.
 If it is available, the module is loaded if necessary, while if it
-is not available, non-zero status is silently returned.
+is not available, non-zero status is silently returned.  The option
+tt(-i) is accepted for compatibility but has no effect.
 
 The var(name)d module is searched for in the same way a command is, using
 tt($module_path) instead of tt($path).  However, the path search is
 performed even when the module name contains a `tt(/)', which it usually does.
 There is no way to prevent the path search.
 
+If the module supports features (see below), tt(zmodload) tries to
+enable all features when loading a module.  If the module was successfully
+loaded but not all features could be enabled, tt(zmodload) returns status 2.
+
 With tt(-u), tt(zmodload) unloads modules.  The same var(name)
 must be given that was given when the module was loaded, but it is not
 necessary for the module to exist in the filesystem.
@@ -1892,6 +1896,54 @@ Each module has a boot and a cleanup function.  The module
 will not be loaded if its boot function fails.  Similarly a module
 can only be unloaded if its cleanup function runs successfully.
 )
+item(tt(zmodload -F) [ tt(-lLe) tt(-P) tt(param) ] var(module) [tt(PLUS()-)]var(feature...))(
+tt(zmodload -F) allows more selective control over the features provided
+by modules.  With no options apart from tt(-F), the module named
+var(module) is loaded, if it was not already loaded, and the list of
+var(feature)s is set to the required state.  If no 
+var(feature)s are specified, the module is loaded, if it was not already
+loaded, but the state of features is unchanged.  Each feature
+may be preceded by a tt(PLUS()) to turn the feature on, or tt(-) to turn it
+off; the tt(PLUS()) is assumed if neither character is present.
+Any feature not explicitly mentioned is left in its current state;
+if the module was not previously loaded this means any such features will
+remain disabled.  The return status is zero if all features were
+set, 1 if the module failed to load, and 2 if some features could
+not be set (for example, a parameter couldn't be added because there
+was a different parameter of the same name) but the module was loaded.
+
+The standard features are builtins, conditions, parameters and math
+functions; these are indicated by the prefix `tt(b:)', `tt(c:)', `tt(p:)'
+and `tt(f:)', respectively, followed by the name that the corresponding
+feature would have in the shell.  For example, `tt(b:strftime)' indicates
+a builtin named tt(strftime) and tt(p:EPOCHSECONDS) indicates a parameter
+named tt(EPOCHSECONDS).  The module may provide other (`abstract') features
+of its own as indicated by its documentation; these have no prefix.
+
+With tt(-l) or tt(-L), features provided by the module are listed.  With
+tt(-l) alone, a list of features together with their states is shown, one
+feature per line.  With tt(-L) alone, a tt(zmodload -F) command that would
+cause enabled features of the module to be turned on is shown.  With
+tt(-lL), a tt(zmodload -F) command that would cause all the features to be
+set to their current state is shown.  If one of these combinations is given
+the option tt(-P) var(param) then the parameter tt(param) is set to an
+array of features, either features together with their state or (if
+tt(-L) alone is given) enabled features.
+
+A set of features may be provided together with tt(-l) or tt(-L); in
+that case only the state of features provided is considered.  Each
+feature may be preceded by tt(PLUS()) or tt(-) but the character
+has no effect.  If no set of features is provided, all features
+are considered.
+
+With tt(-e), the command first tests that the module is loaded;
+if it is not, status 1 is returned.  If the module is loaded,
+the list of features given as an argument is examined.  Any feature
+given with no prefix is simply tested to see if the module provides it;
+any feature given with a prefix tt(PLUS()) or tt(-) is tested to
+see if is provided and in the given state.  If the tests on all features
+in the list succeed, status 0 is returned, else status 1.
+)
 xitem(tt(zmodload) tt(-d) [ tt(-L) ] [ var(name) ])
 xitem(tt(zmodload) tt(-d) var(name) var(dep) ...)
 item(tt(zmodload) tt(-ud) var(name) [ var(dep) ... ])(
diff --git a/Doc/Zsh/compsys.yo b/Doc/Zsh/compsys.yo
index 48df05471..963e43c96 100644
--- a/Doc/Zsh/compsys.yo
+++ b/Doc/Zsh/compsys.yo
@@ -1268,13 +1268,6 @@ If this is set to `true', the tt(_expand_alias) completer and bindable
 command will try to expand disabled aliases, too.  The default is
 `tt(false)'.
 )
-kindex(disable-stat, completion style)
-item(tt(disable-stat))(
-This is used with an empty tag by the tt(_cvs) function
-to decide whether the tt(zsh/stat) module should be used to
-generate names of modified files in the appropriate places (this is its
-only use).  If the style is set, completion will use the tt(ls) command.
-)
 kindex(domains, completion style)
 item(tt(domains))(
 A list of names of network domains for completion.
diff --git a/Doc/Zsh/mod_stat.yo b/Doc/Zsh/mod_stat.yo
index 0354196ad..e0fc15abd 100644
--- a/Doc/Zsh/mod_stat.yo
+++ b/Doc/Zsh/mod_stat.yo
@@ -1,17 +1,24 @@
 COMMENT(!MOD!zsh/stat
 A builtin command interface to the tt(stat) system call.
 !MOD!)
-The tt(zsh/stat) module makes available one builtin command:
+The tt(zsh/stat) module makes available one builtin command under
+two possible names:
 
 startitem()
 findex(stat)
 cindex(files, listing)
 cindex(files, examining)
-item(tt(stat) [ tt(-gnNolLtTrs) ] [ tt(-f) var(fd) ] \
+xitem(tt(zstat) [ tt(-gnNolLtTrs) ] [ tt(-f) var(fd) ] \
     [ tt(-H) var(hash) ] [ tt(-A) var(array) ] \
-    [ tt(-F) var(fmt) ] [ tt(PLUS())var(element) ] [ var(file) ... ])(
+    [ tt(-F) var(fmt) ] [ tt(PLUS())var(element) ] [ var(file) ... ])
+itme(tt(stat) var(...))(
 The command acts as a front end to the tt(stat) system call (see
-manref(stat)(2)).
+manref(stat)(2)).  The same command is provided with two names; as
+the name tt(stat) is often used by an external command it is recommended
+that only the tt(zstat) form of the command is used.  This can be
+arranged by loading the module with the command `tt(zmodload -F zsh/stat
+zstat)'.
+
 If the tt(stat) call fails, the appropriate system error message
 printed and status 1 is returned.
 The fields of tt(struct stat) give information about
@@ -69,7 +76,7 @@ The number of disk blocks used by the file.
 item(tt(link))(
 If the file is a link and the tt(-L) option is in
 effect, this contains the name of the file linked to, otherwise
-it is empty.  Note that if this element is selected (``tt(stat PLUS()link)'')
+it is empty.  Note that if this element is selected (``tt(zstat PLUS()link)'')
 then the tt(-L) option is automatically used.
 )
 enditem()
diff --git a/Etc/zsh-development-guide b/Etc/zsh-development-guide
index 4797f0d02..6e29ed842 100644
--- a/Etc/zsh-development-guide
+++ b/Etc/zsh-development-guide
@@ -213,25 +213,146 @@ following shell variables:
 Be sure to put the values in quotes. For further enlightenment have a look
 at the `mkmakemod.sh' script in the Src directory of the distribution.
 
-Modules have to define four functions which will be called automatically
+Modules have to define six functions which will be called automatically
 by the zsh core. The first one, named `setup_', should set up any data
 needed in the module, at least any data other modules may be interested
-in. The second one, named `boot_', should register all builtins,
-conditional codes, and function wrappers (i.e. anything that will be
-visible to the user) and will be called after the `setup_'-function.
-
-The third one, named `cleanup_', is called when the user tries to unload
-a module and should de-register the builtins etc. The last function,
-`finish_' is called when the module is actually unloaded and should
-finalize all the data initialized in the `setup_'-function.
-
-In short, the `cleanup_'-function should undo what the `boot_'-function
-did, and the `finish_'-function should undo what the `setup_'-function
-did.
+in.
+
+The next pair are features_ and enables_ and deal with enabling module
+features.  Ensure you are familiar with the description of features under
+`zmodload -F'.  The function features_ takes an argument `char
+***featuresp'; *featuresp is to be set to a NULL-terminated array
+containing a list of all the features.  It should then return zero.
+It may return one to indicate features are not supported, but this is
+not recommended.  The function featuresarray conveniently interrogates
+the module's feature strctures for all standard features; space
+is left for abstract features at the end of the array and the names
+must be added by the module.  Note that heap memory should
+be used for this (zhalloc, etc.) as memory for the features array is not
+freed.
+
+A structure "struct features" should
+be used to contain all standard features as well as the number of
+abstract features (those only understood by the module itself).
+It contains pointers to all builtins, conditions, parameters and
+conditions controlled by the module.
+
+enables_ takes an argument `int **enablesp'.  If *enablesp is NULL, it
+should be set to an array of the same length as *featuresp without the
+NULL, containing a 1 for every feature that is enabled and a zero for other
+feature.  By default features are disabled.  If *enablesp is not NULL, its
+values should be used to decide whether features are to be turned off.  It
+should return status 0 for success, 1 on a failure to alter a feature.
+The function handlefeatures() conveniently handles all standard features
+present in the module's features structure; abstract features must
+be handled by the module.  As with features_, any handling of the
+array by the module itself should take into account that the array
+will not be freed and any allocation should therefore be from heap memory.
+
+The functions features_ and enables_ can be called at any point
+after setup_ has been called and before cleanup_ is called.  In
+particular they can be called before or after boot_.
+
+The function named `boot_' should register function wrappers, hooks and
+anything that will be visible to the user that is not handled by features_
+and enables_ (so features should not be turned on here).  It will be called
+after the `setup_'-function, and also after the intial set of features
+have been set by calls to features_ and enables_.
+
+The function named `cleanup_', is called when the user tries to unload
+a module and should de-register all features and hooks.  A call
+to setfeatures with the final argument NULL will remove all standard
+features present in the module's features structure.
+
+The last function, `finish_' is called when the module is actually unloaded
+and should finalize all the data initialized in the `setup_'-function.
+
+In short, the `cleanup_'-function should undo what the `boot_'-function did
+(together with handling any residual effects of enables_), and the
+`finish_'-function should undo what the `setup_'-function did.
 
 All of these functions should return zero if they succeeded and
 non-zero otherwise.
 
+Features
+========
+
+Builtins, conditions, parameters (variables) and math functions
+are described as "features".  They should be made available to
+the shell by declaring a `struct feature' for each module.
+Below are descriptions of the individual features; first here
+is generic information.
+
+`struct feature' contains a pointer to the array that declares each
+feature, followed by the number of entries in the array.  The pointer
+can be NULL and the the size zero for any feature that is not present in
+the module.  For example, to register only builtins in zsh and thereby
+make them visible to the user, the structure should contain
+"bintab" where the array is declared as an array of struct builtin,
+as discussed below:
+
+  static struct feature module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0, /* declare any conditions here */
+    NULL, 0, /* declare any parameters here */
+    NULL, 0, /* declare any math functions here */
+    0, /* number of abstract features */
+  }
+
+Abstract features are handled by the module; the number present
+in `struct features' is there to ensure the main shell allocated
+space in the features and enables array in the standard
+featuresarray() and handlefeatures() calls.  However, the inserting
+of names in the features array and the getting and setting of
+feature enables is left entirely to the module.  Note that abstract
+features should not contain a colon (to avoid clashes with the
+prefixes used in standard features).  It is recommended that
+only alphanumerics, - and _ be used in the names of abstract
+features, and - not be the first character (to avoid confusion
+with disabling features) but this is not required by the main shell.
+
+The features_ and enables_ functions for such a module will look
+like:
+
+  /**/
+  int
+  features_example(Module m, char ***features)
+  {
+     *features = featuresarray(m->nam, &module_features);
+     /* fill in any abstract features in (*features) here */
+     return 0;
+  }
+ 
+  /**/
+  int
+  enables_example(Module m, int **enables)
+  {
+    int ret;
+
+    ret = handlefeatures(m->nam, &module_features, enables);
+    /* handle any abstract features here */
+    ...
+    return ret;
+  }
+
+The functions shown take the name of the module, the set of features,
+
+
+To de-register builtins, pass the features structure to
+setfeatureenables with a NULL final value:
+
+  /**/
+  int
+  cleanup_example(Module m)
+  {
+    setfeatureenables(m->nam, &module_features, NULL);
+    ...
+  }
+
+
+Builtins
+--------
+
 Builtins are described in a table, for example:
 
   static struct builtin bintab[] = {
@@ -280,37 +401,8 @@ integer value from the table (the sixth argument to `BUILTIN(...)').
 The integer return value by the function is the value returned by the
 builtin in shell level.
 
-To register builtins in zsh and thereby making them visible to the
-user the function `addbuiltins()' is used:
-
-  /**/
-  int
-  boot_example(Module m)
-  {
-    int ret;
-
-    ret = addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    ...
-  }
-
-The arguments are the name of the module (taken from the argument in
-the example), the table of definitions and the number of entries in
-this table.
-The return value is 1 if everything went fine, 2 if at least one
-builtin couldn't be defined, and 0 if none of the builtin could be
-defined.
-
-To de-register builtins use the function `deletebuiltins()':
-
-  /**/
-  int
-  cleanup_example(Module m)
-  {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    ...
-  }
-
-The arguments and the return value are the same as for `addbuiltins()'
+Conditions
+----------
 
 The definition of condition codes in modules is equally simple. First
 we need a table with the descriptions:
@@ -374,30 +466,8 @@ tokenized. There are three helper functions available:
     function is non-zero if the the num'th string from the array taken 
     as a glob pattern matches the given string.
 
-Registering and de-registering condition codes with the shell is
-almost exactly the same as for builtins, using the functions
-`addconddefs()' and `deleteconddefs()' instead:
-
-  /**/
-  int
-  boot_example(Module m)
-  {
-    int ret;
-
-    ret = addconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
-    ...
-  }
-
-  /**/
-  int
-  cleanup_example(Module m)
-  {
-    deleteconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
-    ...
-  }
-
-Arguments and return values are the same as for the functions for
-builtins.
+Parameters
+----------
 
 For defining parameters, a module can call `createparam()' directly or 
 use a table to describe them, e.g.:
@@ -443,33 +513,12 @@ initialized to either `NULL' or to a a piece of memory created with
 finish-function of the module because that will be taken care of by
 the `deleteparamdefs()' function described below.
 
-To register the parameters in the zsh core, the function
-`addparamdefs()' is called as in:
-
-  /**/
-  int
-  boot_example(Module m)
-  {
-    int ret;
-
-    ret = addparamdefs(m->nam, patab, sizeof(patab)/sizeof(*patab))
-    ...
-  }
-
-The arguments and the return value are as for the functions used to
-add builtins and condition codes and like these, it should be called
-in the boot-function of the module. To remove the parameters defined,
-the function `deleteparamdefs()' should be called, again with the same 
-arguments and the same return value as for the functions to remove
-builtins and condition codes:
+It is also possible to declare special parameters using
+the macro SPECIALPMDEF().  More care is required in this case.
+See, for example, many of the definitios in Src/Modules/parameter.c.
 
-  /**/
-  int
-  cleanup_example(Module m)
-  {
-    deleteparamdefs(m->nam, patab, sizeof(patab)/sizeof(*patab));
-    ...
-  }
+Math functions
+--------------
 
 Modules can also define math functions. Again, they are described
 using a table:
@@ -531,33 +580,13 @@ union looking like:
 The `type' field should be set to `MN_INTEGER' or `MN_FLOAT' and
 depending on its value either `u.l' or `u.d' contains the value.
 
-To register and de-register math functions, the functions
-`addmathfuncs()' and `deletemathfuncs()' are used:
-
-  /**/
-  int
-  boot_example(Module m)
-  {
-    int ret;
-
-    ret = addmathfuncs(m->nam, mftab, sizeof(mftab)/sizeof(*mftab));
-    ...
-  }
-
-  /**/
-  int
-  cleanup_example(Module m)
-  {
-    deletemathfuncs(m->nam, mftab, sizeof(mftab)/sizeof(*mftab));
-    ...
-  }
-
-The arguments and return values are as for the functions used to
-register and de-register parameters, conditions, etc.
+Hooks
+-----
 
 Modules can also define function hooks. Other modules can then add
 functions to these hooks to make the first module call these functions
-instead of the default.
+instead of the default.  These are not handled by the features
+mechanism as they are not directly visible to the user.
 
 Again, an array is used to define hooks:
 
@@ -629,8 +658,13 @@ that are changed or called very often. These functions,
 structure defining the hook instead of the name and otherwise behave
 like their counterparts.
 
-Finally, modules can define wrapper functions. These functions are
-called whenever a shell function is to be executed.
+Wrappers
+--------
+
+Finally, modules can define wrapper functions.  These functions are
+called whenever a shell function is to be executed.  Again, they
+are not handled by the features mechanism as they are not visible
+to the user.
 
 The definition is simple:
 
diff --git a/Src/Builtins/rlimits.c b/Src/Builtins/rlimits.c
index 88c7602af..8d55d022d 100644
--- a/Src/Builtins/rlimits.c
+++ b/Src/Builtins/rlimits.c
@@ -889,6 +889,14 @@ static struct builtin bintab[] = {
     BUILTIN("unlimit", 0, bin_unlimit, 0, -1, 0, "hs", NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -898,16 +906,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return setfeatureenables(m->nam, &module_features, NULL);
     return 0;
 }
 
diff --git a/Src/Builtins/sched.c b/Src/Builtins/sched.c
index 1d9feab7d..b6b00dff3 100644
--- a/Src/Builtins/sched.c
+++ b/Src/Builtins/sched.c
@@ -333,6 +333,14 @@ static struct builtin bintab[] = {
     BUILTIN("sched", 0, bin_sched, 0, -1, 0, NULL, NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -342,10 +350,23 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-    if(!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)))
-	return 1;
     addprepromptfn(&checksched);
     return 0;
 }
@@ -364,8 +385,7 @@ cleanup_(Module m)
 	zfree(sch, sizeof(*sch));
     }
     delprepromptfn(&checksched);
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/cap.c b/Src/Modules/cap.c
index 875b8c4e5..2886c42b4 100644
--- a/Src/Modules/cap.c
+++ b/Src/Modules/cap.c
@@ -122,6 +122,14 @@ static struct builtin bintab[] = {
     BUILTIN("setcap", 0, bin_setcap, 2, -1, 0, NULL, NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -131,21 +139,35 @@ setup_(UNUSED(Module m))
 
 /**/
 int
-boot_(Module m)
+features_(Module m, char ***features)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
 }
 
 /**/
 int
-cleanup_(Module m)
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
+boot_(UNUSED(Module m))
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     return 0;
 }
 
 /**/
 int
+cleanup_(Module m)
+{
+    return setfeatureenables(m->nam, &module_features, NULL);
+}
+
+/**/
+int
 finish_(UNUSED(Module m))
 {
     return 0;
diff --git a/Src/Modules/clone.c b/Src/Modules/clone.c
index cc303d063..adab4cb59 100644
--- a/Src/Modules/clone.c
+++ b/Src/Modules/clone.c
@@ -109,6 +109,14 @@ static struct builtin bintab[] = {
     BUILTIN("clone", 0, bin_clone, 1, 1, 0, NULL, NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -118,21 +126,35 @@ setup_(UNUSED(Module m))
 
 /**/
 int
-boot_(Module m)
+features_(Module m, char ***features)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
 }
 
 /**/
 int
-cleanup_(Module m)
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
+boot_(UNUSED(Module m))
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     return 0;
 }
 
 /**/
 int
+cleanup_(Module m)
+{
+    return setfeatureenables(m->nam, &module_features, NULL);
+}
+
+/**/
+int
 finish_(UNUSED(Module m))
 {
     return 0;
diff --git a/Src/Modules/datetime.c b/Src/Modules/datetime.c
index add4b303b..06bf52046 100644
--- a/Src/Modules/datetime.c
+++ b/Src/Modules/datetime.c
@@ -154,8 +154,16 @@ static const struct gsu_integer epochseconds_gsu =
 { getcurrentsecs, NULL, stdunsetfn };
 
 static struct paramdef patab[] = {
-    PARAMDEF("EPOCHSECONDS", PM_INTEGER|PM_SPECIAL|PM_READONLY,
-		    NULL, &epochseconds_gsu),
+    SPECIALPMDEF("EPOCHSECONDS", PM_INTEGER|PM_READONLY,
+		 &epochseconds_gsu, NULL, NULL),
+};
+
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    patab, sizeof(patab)/sizeof(*patab),
+    NULL, 0,
+    0
 };
 
 /**/
@@ -167,30 +175,35 @@ setup_(UNUSED(Module m))
 
 /**/
 int
-boot_(Module m)
+features_(Module m, char ***features)
 {
-    return !(addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) |
-	     addparamdefs(m->nam, patab, sizeof(patab)/sizeof(*patab))
-	    );
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
 }
 
 /**/
 int
-cleanup_(Module m)
+enables_(Module m, int **enables)
 {
-    Param pm;
+    return handlefeatures(m->nam, &module_features, enables);
+}
 
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    pm = (Param) paramtab->getnode(paramtab, "EPOCHSECONDS");
-    if (pm && (pm->node.flags & PM_SPECIAL)) {
-	pm->node.flags &= ~PM_READONLY;
-	unsetparam_pm(pm, 0, 1);
-    }
+/**/
+int
+boot_(Module m)
+{
     return 0;
 }
 
 /**/
 int
+cleanup_(Module m)
+{
+    return setfeatureenables(m->nam, &module_features, NULL);
+}
+
+/**/
+int
 finish_(UNUSED(Module m))
 {
     return 0;
diff --git a/Src/Modules/example.c b/Src/Modules/example.c
index ab3a70592..88e910814 100644
--- a/Src/Modules/example.c
+++ b/Src/Modules/example.c
@@ -184,6 +184,14 @@ static struct funcwrap wrapper[] = {
     WRAPDEF(ex_wrapper),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    cotab, sizeof(cotab)/sizeof(*cotab),
+    patab, sizeof(patab)/sizeof(*patab),
+    mftab, sizeof(mftab)/sizeof(*mftab),
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -195,6 +203,21 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     intparam = 42;
@@ -203,23 +226,15 @@ boot_(Module m)
     arrparam[0] = ztrdup("example");
     arrparam[1] = ztrdup("array");
     arrparam[2] = NULL;
-    return !(addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) |
-	     addconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab)) |
-	     addparamdefs(m->nam, patab, sizeof(patab)/sizeof(*patab)) |
-	     addmathfuncs(m->nam, mftab, sizeof(mftab)/sizeof(*mftab)) |
-	     !addwrapper(m, wrapper));
+    return addwrapper(m, wrapper);
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    deleteconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
-    deleteparamdefs(m->nam, patab, sizeof(patab)/sizeof(*patab));
-    deletemathfuncs(m->nam, mftab, sizeof(mftab)/sizeof(*mftab));
     deletewrapper(m, wrapper);
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/files.c b/Src/Modules/files.c
index 079aeac4d..ba742cc50 100644
--- a/Src/Modules/files.c
+++ b/Src/Modules/files.c
@@ -706,6 +706,14 @@ static struct builtin bintab[] = {
     BUILTIN("sync",  0, bin_sync,  0,  0, 0,         NULL,    NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -715,17 +723,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/langinfo.c b/Src/Modules/langinfo.c
index a09c1a0bb..cfbdeed44 100644
--- a/Src/Modules/langinfo.c
+++ b/Src/Modules/langinfo.c
@@ -30,14 +30,10 @@
 #include "langinfo.mdh"
 #include "langinfo.pro"
 
-static char langinfo_nam[] = "langinfo";
-
 #ifdef HAVE_LANGINFO_H
 # include <langinfo.h>
 #endif
 
-static Param langinfo_pm;
-
 /**/
 #ifdef HAVE_NL_LANGINFO
 
@@ -396,46 +392,6 @@ liitem(char *name)
 }
 
 /**/
-static void
-shempty(void)
-{
-}
-
-/* Create a simple special hash parameter. */
-
-/**/
-static Param
-createlihash()
-{
-    Param pm;
-    HashTable ht;
-
-    unsetparam(langinfo_nam);
-
-    if (!(pm = createparam(langinfo_nam, PM_SPECIAL|PM_HIDE|PM_HIDEVAL|
-			   PM_REMOVABLE|PM_HASHED)))
-	return NULL;
-
-    pm->level = pm->old ? locallevel : 0;
-    pm->gsu.h = &stdhash_gsu;
-    pm->u.hash = ht = newhashtable(7, langinfo_nam, NULL);
-
-    ht->hash        = hasher;
-    ht->emptytable  = (TableFunc) shempty;
-    ht->filltable   = NULL;
-    ht->addnode     = (AddNodeFunc) shempty;
-    ht->getnode     = ht->getnode2 = getlanginfo;
-    ht->removenode  = (RemoveNodeFunc) shempty;
-    ht->disablenode = NULL;
-    ht->enablenode  = NULL;
-    ht->freenode    = (FreeNodeFunc) shempty;
-    ht->printnode   = printparamnode;
-    ht->scantab     = scanlanginfo;
-
-    return (langinfo_pm = pm);
-}
-
-/**/
 static HashNode
 getlanginfo(UNUSED(HashTable ht), char *name)
 {
@@ -490,9 +446,25 @@ scanlanginfo(UNUSED(HashTable ht), ScanFunc func, int flags)
     
 }
 
+static struct paramdef partab[] = {
+    SPECIALPMDEF("langinfo", 0, NULL, getlanginfo, scanlanginfo)
+};
+
 /**/
 #endif /* HAVE_NL_LANGINFO */
 
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+#ifdef HAVE_NL_LANGINFO
+    partab, sizeof(partab)/sizeof(*partab),
+#else
+    NULL, 0,
+#endif
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -502,35 +474,35 @@ setup_(UNUSED(Module m))
 
 /**/
 int
-boot_(UNUSED(Module m))
+features_(Module m, char ***features)
 {
-#ifdef HAVE_NL_LANGINFO
-    if (!createlihash())
-    	return 1;
-#else
-    unsetparam(langinfo_nam);
-#endif
+    *features = featuresarray(m->nam, &module_features);
     return 0;
 }
 
 /**/
 int
-cleanup_(UNUSED(Module m))
+enables_(Module m, int **enables)
 {
-#ifdef HAVE_NL_LANGINFO
-    Param pm;
+    return handlefeatures(m->nam, &module_features, enables);
+}
 
-    if ((pm = (Param) paramtab->getnode(paramtab, langinfo_nam)) &&
-	pm == langinfo_pm) {
-	pm->node.flags &= ~PM_READONLY;
-	unsetparam_pm(pm, 0, 1);
-    }
-#endif
+/**/
+int
+boot_(UNUSED(Module m))
+{
     return 0;
 }
 
 /**/
 int
+cleanup_(UNUSED(Module m))
+{
+    return setfeatureenables(m->nam, &module_features, NULL);
+}
+
+/**/
+int
 finish_(UNUSED(Module m))
 {
     return 0;
diff --git a/Src/Modules/langinfo.mdd b/Src/Modules/langinfo.mdd
index a3a615113..66c4cd452 100644
--- a/Src/Modules/langinfo.mdd
+++ b/Src/Modules/langinfo.mdd
@@ -1,6 +1,6 @@
 name=zsh/langinfo
 
-link=either
+link=`if test x$ac_cv_func_nl_langinfo; then echo either; else echo no; fi`
 load=no
 
 autoparams="langinfo"
diff --git a/Src/Modules/mapfile.c b/Src/Modules/mapfile.c
index 9f3ca2612..25b506f03 100644
--- a/Src/Modules/mapfile.c
+++ b/Src/Modules/mapfile.c
@@ -58,60 +58,9 @@
 #endif /* HAVE_MMAP && HAVE_MUNMAP && HAVE_MSYNC */
 #endif /* HAVE_SYS_MMAN_H &&  HAVE_FTRUNCATE */
 
-/*
- * Name of the special parameter.  If zmodload took arguments,
- * we could make this selectable.
- */
-static char mapfile_nam[] = "mapfile";
-
-static Param mapfile_pm;
-
-/* Empty dummy function for special hash parameters. */
-
-/**/
-static void
-shempty(void)
-{
-}
-
 static const struct gsu_hash mapfiles_gsu =
 { hashgetfn, setpmmapfiles, stdunsetfn };
 
-/* Create the special hash parameter. */
-
-/**/
-static Param
-createmapfilehash()
-{
-    Param pm;
-    HashTable ht;
-
-    unsetparam(mapfile_nam);
-    mapfile_pm = NULL;
-
-    if (!(pm = createparam(mapfile_nam, PM_SPECIAL|PM_HIDE|PM_HIDEVAL|
-			   PM_REMOVABLE|PM_HASHED)))
-	return NULL;
-
-    pm->level = pm->old ? locallevel : 0;
-    pm->gsu.h = &mapfiles_gsu;
-    pm->u.hash = ht = newhashtable(7, mapfile_nam, NULL);
-
-    ht->hash        = hasher;
-    ht->emptytable  = (TableFunc) shempty;
-    ht->filltable   = NULL;
-    ht->addnode     = (AddNodeFunc) shempty;
-    ht->getnode     = ht->getnode2 = getpmmapfile;
-    ht->removenode  = (RemoveNodeFunc) shempty;
-    ht->disablenode = NULL;
-    ht->enablenode  = NULL;
-    ht->freenode    = (FreeNodeFunc) shempty;
-    ht->printnode   = printparamnode;
-    ht->scantab     = scanpmmapfile;
-
-    return (mapfile_pm = pm);
-}
-
 /* Functions for the options special parameter. */
 
 /**/
@@ -192,9 +141,6 @@ setpmmapfiles(Param pm, HashTable ht)
     int i;
     HashNode hn;
 
-    /* just to see if I've understood what's happening */
-    DPUTS(pm != mapfile_pm, "BUG: setpmmapfiles called for wrong param");
-
     if (!ht)
 	return;
 
@@ -261,6 +207,10 @@ get_contents(char *fname)
 static const struct gsu_scalar mapfile_gsu =
 { strgetfn, setpmmapfile, unsetpmmapfile };
 
+static struct paramdef partab[] = {
+    SPECIALPMDEF("mapfile", 0, &mapfiles_gsu, getpmmapfile, scanpmmapfile)
+};
+
 /**/
 static HashNode
 getpmmapfile(UNUSED(HashTable ht), char *name)
@@ -272,7 +222,7 @@ getpmmapfile(UNUSED(HashTable ht), char *name)
     pm->node.nam = dupstring(name);
     pm->node.flags = PM_SCALAR;
     pm->gsu.s = &mapfile_gsu;
-    pm->node.flags |= (mapfile_pm->node.flags & PM_READONLY);
+    pm->node.flags |= (partab[0].pm->node.flags & PM_READONLY);
 
     /* Set u.str to contents of file given by name */
     if ((contents = get_contents(pm->node.nam)))
@@ -298,7 +248,7 @@ scanpmmapfile(UNUSED(HashTable ht), ScanFunc func, int flags)
     memset((void *)&pm, 0, sizeof(struct param));
     pm.node.flags = PM_SCALAR;
     pm.gsu.s = &mapfile_gsu;
-    pm.node.flags |= (mapfile_pm->node.flags & PM_READONLY);
+    pm.node.flags |= (partab[0].pm->node.flags & PM_READONLY);
 
     /* Here we scan the current directory, calling func() for each file */
     while ((pm.node.nam = zreaddir(dir, 1))) {
@@ -315,6 +265,14 @@ scanpmmapfile(UNUSED(HashTable ht), ScanFunc func, int flags)
     closedir(dir);
 }
 
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -324,13 +282,23 @@ setup_(UNUSED(Module m))
 
 /**/
 int
-boot_(UNUSED(Module m))
+features_(Module m, char ***features)
 {
-    /* Create the special associative array. */
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
 
-    if (!createmapfilehash())
-	return 1;
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
 
+/**/
+int
+boot_(UNUSED(Module m))
+{
     return 0;
 }
 
@@ -338,16 +306,7 @@ boot_(UNUSED(Module m))
 int
 cleanup_(UNUSED(Module m))
 {
-    Param pm;
-
-    /* Remove the special parameter if it is still the same. */
-
-    if ((pm = (Param) paramtab->getnode(paramtab, mapfile_nam)) &&
-	pm == mapfile_pm) {
-	pm->node.flags &= ~PM_READONLY;
-	unsetparam_pm(pm, 0, 1);
-    }
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/mathfunc.c b/Src/Modules/mathfunc.c
index f6d437ff9..a473476e3 100644
--- a/Src/Modules/mathfunc.c
+++ b/Src/Modules/mathfunc.c
@@ -561,6 +561,14 @@ math_string(UNUSED(char *name), char *arg, int id)
 }
 
 
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    mftab, sizeof(mftab)/sizeof(*mftab),
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -570,17 +578,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-    return !addmathfuncs(m->nam, mftab, sizeof(mftab)/sizeof(*mftab));
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletemathfuncs(m->nam, mftab, sizeof(mftab)/sizeof(*mftab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c
index 7b790acc6..9d52bcd3f 100644
--- a/Src/Modules/parameter.c
+++ b/Src/Modules/parameter.c
@@ -34,46 +34,6 @@
 
 static int incleanup;
 
-/* Empty dummy function for special hash parameters. */
-
-/**/
-static void
-shempty(void)
-{
-}
-
-/* Create a simple special hash parameter. */
-
-/**/
-static Param
-createspecialhash(char *name, GetNodeFunc get, ScanTabFunc scan)
-{
-    Param pm;
-    HashTable ht;
-
-    if (!(pm = createparam(name, PM_SPECIAL|PM_HIDE|PM_HIDEVAL|
-			   PM_REMOVABLE|PM_HASHED)))
-	return NULL;
-
-    pm->level = pm->old ? locallevel : 0;
-    pm->gsu.h = &stdhash_gsu;
-    pm->u.hash = ht = newhashtable(0, name, NULL);
-
-    ht->hash        = hasher;
-    ht->emptytable  = (TableFunc) shempty;
-    ht->filltable   = NULL;
-    ht->addnode     = (AddNodeFunc) shempty;
-    ht->getnode     = ht->getnode2 = get;
-    ht->removenode  = (RemoveNodeFunc) shempty;
-    ht->disablenode = NULL;
-    ht->enablenode  = NULL;
-    ht->freenode    = (FreeNodeFunc) shempty;
-    ht->printnode   = printparamnode;
-    ht->scantab     = scan;
-
-    return pm;
-}
-
 /* Functions for the parameters special parameter. */
 
 /* Return a string describing the type of a parameter. */
@@ -1838,13 +1798,6 @@ struct pardef {
     Param pm;
 };
 
-/*
- * This is a duplicate of nullsethash_gsu.  On some systems
- * (such as Cygwin) we can't put a pointer to an imported variable
- * in a compile-time initialiser, so we use this instead.
- */
-static const struct gsu_hash pmnullsethash_gsu =
-{ hashgetfn, nullsethashfn, nullunsetfn };
 static const struct gsu_hash pmcommands_gsu =
 { hashgetfn, setpmcommands, stdunsetfn };
 static const struct gsu_hash pmfunctions_gsu =
@@ -1881,149 +1834,117 @@ static const struct gsu_array dirs_gsu =
 static const struct gsu_array historywords_gsu =
 { histwgetfn, arrsetfn, stdunsetfn };
 
-static struct pardef partab[] = {
-    { "parameters", PM_READONLY,
-      getpmparameter, scanpmparameters, &pmnullsethash_gsu,
-      NULL, NULL },
-    { "commands", 0,
-      getpmcommand, scanpmcommands, &pmcommands_gsu,
-      NULL, NULL },
-    { "functions", 0,
-      getpmfunction, scanpmfunctions, &pmfunctions_gsu,
-      NULL, NULL },
-    { "dis_functions", 0,
-      getpmdisfunction, scanpmdisfunctions, &pmdisfunctions_gsu,
-      NULL, NULL },
-    { "funcstack", PM_ARRAY|PM_SPECIAL|PM_READONLY,
-      NULL, NULL, NULL,
-      &funcstack_gsu, NULL },
-    { "functrace", PM_ARRAY|PM_SPECIAL|PM_READONLY,
-      NULL, NULL, NULL,
-      &functrace_gsu, NULL },
-    { "builtins", PM_READONLY,
-      getpmbuiltin, scanpmbuiltins, NULL,
-      NULL, NULL },
-    { "dis_builtins", PM_READONLY,
-      getpmdisbuiltin, scanpmdisbuiltins, NULL,
-      NULL, NULL },
-    { "reswords", PM_ARRAY|PM_SPECIAL|PM_READONLY,
-      NULL, NULL, NULL,
-      &reswords_gsu, NULL },
-    { "dis_reswords", PM_ARRAY|PM_SPECIAL|PM_READONLY,
-      NULL, NULL, NULL,
-      &disreswords_gsu, NULL },
-    { "options", 0,
-      getpmoption, scanpmoptions, &pmoptions_gsu,
-      NULL, NULL },
-    { "modules", PM_READONLY,
-      getpmmodule, scanpmmodules, NULL,
-      NULL, NULL },
-    { "dirstack", PM_ARRAY|PM_SPECIAL|PM_REMOVABLE,
-      NULL, NULL, NULL,
-      &dirs_gsu, NULL },
-    { "history", PM_READONLY,
-      getpmhistory, scanpmhistory, NULL,
-      NULL, NULL,  },
-    { "historywords", PM_ARRAY|PM_SPECIAL|PM_READONLY,
-      NULL, NULL, NULL,
-      &historywords_gsu, NULL },
-    { "jobtexts", PM_READONLY,
-      getpmjobtext, scanpmjobtexts, NULL,
-      NULL, NULL },
-    { "jobstates", PM_READONLY,
-      getpmjobstate, scanpmjobstates, NULL,
-      NULL, NULL },
-    { "jobdirs", PM_READONLY,
-      getpmjobdir, scanpmjobdirs, NULL,
-      NULL, NULL },
-    { "nameddirs", 0,
-      getpmnameddir, scanpmnameddirs, &pmnameddirs_gsu,
-      NULL, NULL },
-    { "userdirs", PM_READONLY,
-      getpmuserdir, scanpmuserdirs, NULL,
-      NULL, NULL },
-    { "aliases", 0,
-      getpmralias, scanpmraliases, &pmraliases_gsu,
-      NULL, NULL },
-    { "galiases", 0,
-      getpmgalias, scanpmgaliases, &pmgaliases_gsu,
-      NULL, NULL },
-    { "saliases", 0,
-      getpmsalias, scanpmsaliases, &pmsaliases_gsu,
-      NULL, NULL },
-    { "dis_aliases", 0,
-      getpmdisralias, scanpmdisraliases, &pmdisraliases_gsu,
-      NULL, NULL },
-    { "dis_galiases", 0,
-      getpmdisgalias, scanpmdisgaliases, &pmdisgaliases_gsu,
-      NULL, NULL },
-    { "dis_saliases", 0,
-      getpmdissalias, scanpmdissaliases, &pmdissaliases_gsu,
-      NULL, NULL },
-    { NULL, 0, NULL, NULL, NULL, NULL, NULL }
+static struct paramdef partab[] = {
+    SPECIALPMDEF("parameters", PM_READONLY,
+	    NULL, getpmparameter, scanpmparameters),
+    SPECIALPMDEF("commands", 0, &pmcommands_gsu, getpmcommand, scanpmcommands),
+    SPECIALPMDEF("functions", 0, &pmfunctions_gsu, getpmfunction,
+		 scanpmfunctions),
+    SPECIALPMDEF("dis_functions", 0, 
+	    &pmdisfunctions_gsu, getpmdisfunction, scanpmdisfunctions),
+    SPECIALPMDEF("funcstack", PM_ARRAY|PM_READONLY,
+	    &funcstack_gsu, NULL, NULL),
+    SPECIALPMDEF("functrace", PM_ARRAY|PM_READONLY,
+	    &functrace_gsu, NULL, NULL),
+    SPECIALPMDEF("builtins", PM_READONLY, NULL, getpmbuiltin, scanpmbuiltins),
+    SPECIALPMDEF("dis_builtins", PM_READONLY,
+	    NULL, getpmdisbuiltin, scanpmdisbuiltins),
+    SPECIALPMDEF("reswords", PM_ARRAY|PM_READONLY,
+	    &reswords_gsu, NULL, NULL),
+    SPECIALPMDEF("dis_reswords", PM_ARRAY|PM_READONLY,
+	    &disreswords_gsu, NULL, NULL),
+    SPECIALPMDEF("options", 0,
+	    &pmoptions_gsu, getpmoption, scanpmoptions),
+    SPECIALPMDEF("modules", PM_READONLY,
+	    NULL, getpmmodule, scanpmmodules),
+    SPECIALPMDEF("dirstack", PM_ARRAY,
+	    &dirs_gsu, NULL, NULL),
+    SPECIALPMDEF("history", PM_READONLY,
+	    NULL, getpmhistory, scanpmhistory),
+    SPECIALPMDEF("historywords", PM_ARRAY|PM_READONLY,
+	    &historywords_gsu, NULL, NULL),
+    SPECIALPMDEF("jobtexts", PM_READONLY,
+	    NULL, getpmjobtext, scanpmjobtexts),
+    SPECIALPMDEF("jobstates", PM_READONLY,
+	    NULL, getpmjobstate, scanpmjobstates),
+    SPECIALPMDEF("jobdirs", PM_READONLY,
+	    NULL, getpmjobdir, scanpmjobdirs),
+    SPECIALPMDEF("nameddirs", 0,
+	    &pmnameddirs_gsu, getpmnameddir, scanpmnameddirs),
+    SPECIALPMDEF("userdirs", PM_READONLY,
+	    NULL, getpmuserdir, scanpmuserdirs),
+    SPECIALPMDEF("aliases", 0,
+	    &pmraliases_gsu, getpmralias, scanpmraliases),
+    SPECIALPMDEF("galiases", 0,
+	    &pmgaliases_gsu, getpmgalias, scanpmgaliases),
+    SPECIALPMDEF("saliases", 0,
+	    &pmsaliases_gsu, getpmsalias, scanpmsaliases),
+    SPECIALPMDEF("dis_aliases", 0,
+	    &pmdisraliases_gsu, getpmdisralias, scanpmdisraliases),
+    SPECIALPMDEF("dis_galiases", 0,
+	    &pmdisgaliases_gsu, getpmdisgalias, scanpmdisgaliases),
+    SPECIALPMDEF("dis_saliases", 0,
+	    &pmdissaliases_gsu, getpmdissalias, scanpmdissaliases)
+};
+
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    NULL, 0,
+    0
 };
 
 /**/
 int
 setup_(UNUSED(Module m))
 {
-    incleanup = 0;
-
     return 0;
 }
 
 /**/
 int
-boot_(UNUSED(Module m))
+features_(Module m, char ***features)
 {
-    /* Create the special associative arrays.
-     * As an example for autoloaded parameters, this is probably a bad
-     * example, because the zsh core doesn't support creation of
-     * special hashes, yet. */
-
-    struct pardef *def;
-
-    for (def = partab; def->name; def++) {
-	unsetparam(def->name);
-
-	if (def->getnfn) {
-	    if (!(def->pm = createspecialhash(def->name, def->getnfn,
-					      def->scantfn)))
-		return 1;
-	    def->pm->node.flags |= def->flags;
-	    if (def->hash_gsu)
-		def->pm->gsu.h = def->hash_gsu;
-	} else {
-	    if (!(def->pm = createparam(def->name, def->flags | PM_HIDE|
-					PM_HIDEVAL | PM_REMOVABLE)))
-		return 1;
-	    def->pm->gsu.a = def->array_gsu;
-	}
-    }
+    *features = featuresarray(m->nam, &module_features);
     return 0;
 }
 
 /**/
 int
-cleanup_(UNUSED(Module m))
+enables_(Module m, int **enables)
 {
-    Param pm;
-    struct pardef *def;
-
+    int ret;
+    /*
+     * If we remove features, we shouldn't have an effect
+     * on the main shell, so set the flag to indicate.
+     */
     incleanup = 1;
+    ret = handlefeatures(m->nam, &module_features, enables);
+    incleanup = 0;
+    return ret;
+}
 
-    for (def = partab; def->name; def++) {
-	if ((pm = (Param) paramtab->getnode(paramtab, def->name)) &&
-	    pm == def->pm) {
-	    pm->node.flags &= ~PM_READONLY;
-	    unsetparam_pm(pm, 0, 1);
-	}
-    }
+/**/
+int
+boot_(Module m)
+{
     return 0;
 }
 
 /**/
 int
+cleanup_(Module m)
+{
+    int ret;
+    incleanup = 1;
+    ret = setfeatureenables(m->nam, &module_features, NULL);
+    incleanup = 0;
+    return ret;
+}
+
+/**/
+int
 finish_(UNUSED(Module m))
 {
     return 0;
diff --git a/Src/Modules/pcre.c b/Src/Modules/pcre.c
index d067d8949..45c38eba0 100644
--- a/Src/Modules/pcre.c
+++ b/Src/Modules/pcre.c
@@ -295,6 +295,19 @@ static struct builtin bintab[] = {
 };
 
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+#if defined(HAVE_PCRE_COMPILE) && defined(HAVE_PCRE_EXEC)
+    cotab, sizeof(cotab)/sizeof(*cotab),
+#else /* !(HAVE_PCRE_COMPILE && HAVE_PCRE_EXEC) */
+    NULL, 0,
+#endif /* !(HAVE_PCRE_COMPILE && HAVE_PCRE_EXEC) */
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -304,25 +317,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-#if defined(HAVE_PCRE_COMPILE) && defined(HAVE_PCRE_EXEC)
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) ||
-	   !addconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
-#else /* !(HAVE_PCRE_COMPILE && HAVE_PCRE_EXEC) */
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-#endif /* !(HAVE_PCRE_COMPILE && HAVE_PCRE_EXEC) */
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-#if defined(HAVE_PCRE_COMPILE) && defined(HAVE_PCRE_EXEC)
-    deleteconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
-#endif /* !(HAVE_PCRE_COMPILE && HAVE_PCRE_EXEC) */
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/regex.c b/Src/Modules/regex.c
index 44019a1b9..a3d956055 100644
--- a/Src/Modules/regex.c
+++ b/Src/Modules/regex.c
@@ -131,6 +131,16 @@ static struct conddef cotab[] = {
     CONDDEF("regex-match", CONDF_INFIX, zcond_regex_match, 0, 0, ZREGEX_EXTENDED)
 };
 
+
+static struct features module_features = {
+    NULL, 0,
+    cotab, sizeof(cotab)/sizeof(*cotab),
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -140,17 +150,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-    return !addconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deleteconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/socket.c b/Src/Modules/socket.c
index 7ca56b4e7..413625dcf 100644
--- a/Src/Modules/socket.c
+++ b/Src/Modules/socket.c
@@ -255,6 +255,14 @@ static struct builtin bintab[] = {
     BUILTIN("zsocket", 0, bin_zsocket, 0, 3, 0, "ad:ltv", NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /* The load/unload routines required by the zsh library interface */
 
 /**/
@@ -266,18 +274,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
-boot_(Module m)
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return handlefeatures(m->nam, &module_features, enables);
 }
 
+/**/
+int
+boot_(Module m)
+{
+    return 0;
+}
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/stat.c b/Src/Modules/stat.c
index 3ffaf9d4d..1d55317ea 100644
--- a/Src/Modules/stat.c
+++ b/Src/Modules/stat.c
@@ -621,6 +621,15 @@ bin_stat(char *name, char **args, Options ops, UNUSED(int func))
 
 static struct builtin bintab[] = {
     BUILTIN("stat", 0, bin_stat, 0, -1, 0, NULL, NULL),
+    BUILTIN("zstat", 0, bin_stat, 0, -1, 0, NULL, NULL),
+};
+
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
 };
 
 /**/
@@ -632,17 +641,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/system.c b/Src/Modules/system.c
index 1eaa2fabd..f8a188d42 100644
--- a/Src/Modules/system.c
+++ b/Src/Modules/system.c
@@ -364,47 +364,70 @@ static const struct gsu_array errnos_gsu =
 /* Functions for the sysparams special parameter. */
 
 /**/
-static char *
-sysparamgetfn(Param pm)
+static void
+fillpmsysparams(Param pm, char *name)
 {
     char buf[DIGBUFSIZE];
     int num;
 
-    if (!strcmp(pm->node.nam, "pid")) {
+    pm->node.nam = dupstring(name);
+    pm->node.flags = PM_SCALAR | PM_READONLY;
+    pm->gsu.s = &nullsetscalar_gsu;
+    if (!strcmp(name, "pid")) {
 	num = (int)getpid();
-    } else if (!strcmp(pm->node.nam, "ppid")) {
+    } else if (!strcmp(name, "ppid")) {
 	num = (int)getppid();
-    }
-    else {
-#ifdef DEBUG
-	dputs("Bad sysparam parameter");
-#endif
-	return "";
+    } else {
+	pm->u.str = dupstring("");
+	pm->node.flags |= PM_UNSET;
+	return;
     }
 
     sprintf(buf, "%d", num);
-    return dupstring(buf);
+    pm->u.str = dupstring(buf);
 }
 
-static const struct gsu_scalar sysparam_gsu =
-{ sysparamgetfn, strsetfn, stdunsetfn };
 
+/**/
+static HashNode
+getpmsysparams(UNUSED(HashTable ht), char *name)
+{
+    Param pm;
+
+    pm = (Param) hcalloc(sizeof(struct param));
+    fillpmsysparams(pm, name);
+    return &pm->node;
+}
+
+
+/**/
 static void
-fixsysparams(HashNode hn, int flags)
+scanpmsysparams(UNUSED(HashTable ht), ScanFunc func, int flags)
 {
-    Param pm = (Param)hn;
+    struct param spm;
 
-    if (flags) {
-	/* prepare to free */
-	pm->node.flags &= ~PM_READONLY;
-    } else {
-	/* assign */
-	pm->gsu.s = &sysparam_gsu;
-	pm->node.flags |= PM_READONLY;
-    }
+    fillpmsysparams(&spm, "pid");
+    func(&spm.node, flags);
+    fillpmsysparams(&spm, "ppid");
+    func(&spm.node, flags);
 }
 
 
+static struct paramdef partab[] = {
+    SPECIALPMDEF("errnos", PM_ARRAY|PM_READONLY,
+		 &errnos_gsu, NULL, NULL),
+    SPECIALPMDEF("sysparams", PM_READONLY,
+		 NULL, getpmsysparams, scanpmsysparams)
+};
+
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    NULL, 0,
+    0
+};
+
 /* The load/unload routines required by the zsh library interface */
 
 /**/
@@ -415,61 +438,24 @@ setup_(UNUSED(Module m))
 }
 
 /**/
-static void
-tidyparam(Param pm)
+int
+features_(Module m, char ***features)
 {
-    if (!pm)
-	return;
-    pm->node.flags &= ~PM_READONLY;
-    unsetparam_pm(pm, 0, 1);
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
 }
 
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
 
 /**/
 int
 boot_(Module m)
 {
-    Param pm_nos, pm_params;
-    HashTable ht;
-    const char *sysparams_args[] = {
-	"pid", 	"ppid", NULL
-    }, **srcptr;
-    char **arglist, **dstptr;
-
-    /* this takes care of an autoload on errnos */
-    unsetparam("errnos");
-    if (!(pm_nos = createparam("errnos", PM_ARRAY|PM_SPECIAL|PM_READONLY|
-			       PM_HIDE|PM_HIDEVAL|PM_REMOVABLE)))
-	return 1;
-    pm_nos->gsu.a = &errnos_gsu;
-
-    if (!(pm_params = createparam("sysparams", PM_HASHED|PM_SPECIAL|
-				  PM_HIDE|PM_HIDEVAL|PM_REMOVABLE))) {
-	tidyparam(pm_nos);
-	return 1;
-    }
-    pm_params->level = pm_params->old ? locallevel : 0;
-    pm_params->gsu.h = &stdhash_gsu;
-    pm_params->u.hash = ht = newparamtable(0, "sysparams");
-
-    arglist = (char **)zshcalloc((2*arrlen((char **)sysparams_args) + 1) *
-			       sizeof(char *));
-    for (srcptr = sysparams_args, dstptr = arglist; *srcptr; ) {
-	*dstptr++ = ztrdup(*srcptr++);
-	*dstptr++ = ztrdup("");
-    }
-    *dstptr = NULL;
-    /* make sure we don't overwrite the hash table: use the "augment" arg */
-    arrhashsetfn(pm_params, arglist, 1);
-    scanhashtable(ht, 0, 0, 0, fixsysparams, 0);
-
-    pm_params->node.flags |= PM_READONLY;
-
-    if (!addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab))) {
-	tidyparam(pm_nos);
-	tidyparam(pm_params);
-	return 1;
-    }
     return 0;
 }
 
@@ -478,17 +464,7 @@ boot_(Module m)
 int
 cleanup_(Module m)
 {
-    Param pm;
-    if ((pm = (Param)paramtab->getnode(paramtab, "errnos")))
-	tidyparam(pm);
-    if ((pm = (Param)paramtab->getnode(paramtab, "sysparams")))
-    {
-	scanhashtable(pm->u.hash, 0, 0, 0, fixsysparams, 1);
-	tidyparam(pm);
-    }
-
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/tcp.c b/Src/Modules/tcp.c
index 2484edfe3..86803e952 100644
--- a/Src/Modules/tcp.c
+++ b/Src/Modules/tcp.c
@@ -675,6 +675,14 @@ static struct builtin bintab[] = {
     BUILTIN("ztcp", 0, bin_ztcp, 0, 3, 0, "acd:flLtv", NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /* The load/unload routines required by the zsh library interface */
 
 /**/
@@ -686,10 +694,25 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     ztcp_sessions = znewlinklist();
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
 }
 
 
@@ -698,9 +721,8 @@ int
 cleanup_(Module m)
 {
     tcp_cleanup();
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     freelinklist(ztcp_sessions, (FreeFunc) ztcp_free_session);
-    return 0;
+    return setfeatureeanbles(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/termcap.c b/Src/Modules/termcap.c
index e29b2bf74..c19db4892 100644
--- a/Src/Modules/termcap.c
+++ b/Src/Modules/termcap.c
@@ -48,8 +48,7 @@
 #include "termcap.mdh"
 #include "termcap.pro"
 
-static char termcap_nam[] = "termcap";
-
+/**/
 #ifdef HAVE_TGETENT
 # ifdef USES_TERM_H
 #  ifdef HAVE_TERMIO_H
@@ -65,8 +64,6 @@ static char termcap_nam[] = "termcap";
 #  endif
 # endif
 
-static Param termcap_pm;
-
 #ifndef HAVE_BOOLCODES
 static char *boolcodes[] = {
     "bw", "am", "ut", "cc", "xs", "YA", "YF", "YB", "xt", "xn", "eo",
@@ -161,62 +158,11 @@ bin_echotc(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
     return 0;
 }
 
-#else /* ! HAVE_TGETENT */
-
-#define bin_echotc bin_notavail
-
-#endif /* HAVE_TGETENT */
-
 static struct builtin bintab[] = {
     BUILTIN("echotc", 0, bin_echotc, 1, -1, 0, NULL, NULL),
 };
 
 /**/
-#ifdef HAVE_TGETENT
-
-/* Empty dummy function for special hash parameters. */
-
-/**/
-static void
-shempty(void)
-{
-}
-
-/* Create a simple special hash parameter. */
-
-/**/
-static Param
-createtchash()
-{
-    Param pm;
-    HashTable ht;
-
-    unsetparam(termcap_nam);
-
-    if (!(pm = createparam(termcap_nam, PM_SPECIAL|PM_HIDE|PM_HIDEVAL|
-			   PM_REMOVABLE|PM_HASHED)))
-	return NULL;
-
-    pm->level = pm->old ? locallevel : 0;
-    pm->gsu.h = &stdhash_gsu;
-    pm->u.hash = ht = newhashtable(7, termcap_nam, NULL);
-
-    ht->hash        = hasher;
-    ht->emptytable  = (TableFunc) shempty;
-    ht->filltable   = NULL;
-    ht->addnode     = (AddNodeFunc) shempty;
-    ht->getnode     = ht->getnode2 = gettermcap;
-    ht->removenode  = (RemoveNodeFunc) shempty;
-    ht->disablenode = NULL;
-    ht->enablenode  = NULL;
-    ht->freenode    = (FreeNodeFunc) shempty;
-    ht->printnode   = printparamnode;
-    ht->scantab     = scantermcap;
-
-    return (termcap_pm = pm);
-}
-
-/**/
 static HashNode
 gettermcap(UNUSED(HashTable ht), char *name)
 {
@@ -364,9 +310,29 @@ scantermcap(UNUSED(HashTable ht), ScanFunc func, int flags)
     }
 }
 
+struct paramdef partab[] = {
+    SPECIALPMDEF("termcap", PM_READONLY, NULL, gettermcap, scantermcap)
+};
+
 /**/
 #endif /* HAVE_TGETENT */
 
+static struct features module_features = {
+#ifdef HAVE_TGETENT
+    bintab, sizeof(bintab)/sizeof(*bintab),
+#else
+    NULL, 0,
+#endif
+    NULL, 0,
+#ifdef HAVE_TGETENT
+    partab, sizeof(partab)/sizeof(*partab),
+#else
+    NULL, 0,
+#endif
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -376,36 +342,36 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
 #ifdef HAVE_TGETENT
 # ifdef HAVE_SETUPTERM
     setupterm((char *)0, 1, (int *)0);
 # endif
-
-    if (!createtchash())
-    	return 1;
-#else
-    unsetparam(termcap_nam);
 #endif
-    return  !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return  0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-#ifdef HAVE_TGETENT
-    Param pm;
-
-    if ((pm = (Param) paramtab->getnode(paramtab, termcap_nam)) &&
-	pm == termcap_pm) {
-	pm->node.flags &= ~PM_READONLY;
-	unsetparam_pm(pm, 0, 1);
-    }
-#endif
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/terminfo.c b/Src/Modules/terminfo.c
index b4a1c599b..d324c3a6c 100644
--- a/Src/Modules/terminfo.c
+++ b/Src/Modules/terminfo.c
@@ -37,7 +37,6 @@
 #endif
 
 #include "terminfo.pro"
-static char terminfo_nam[] = "terminfo";
 
 /**/
 #ifdef USE_TERMINFO_MODULE
@@ -55,8 +54,6 @@ static char terminfo_nam[] = "terminfo";
 #  include <term.h>
 # endif
 
-static Param terminfo_pm;
-
 /* echoti: output a terminfo capability */
 
 /**/
@@ -126,64 +123,11 @@ bin_echoti(char *name, char **argv, UNUSED(Options ops), UNUSED(int func))
     return 0;
 }
 
-/**/
-#else /* !USE_TERMINFO_MODULE */
-
-#define bin_echoti bin_notavail
-
-/**/
-#endif /* !USE_TERMINFO_MODULE */
-
 static struct builtin bintab[] = {
     BUILTIN("echoti", 0, bin_echoti, 1, -1, 0, NULL, NULL),
 };
 
 /**/
-#ifdef USE_TERMINFO_MODULE
-
-/* Empty dummy function for special hash parameters. */
-
-/**/
-static void
-shempty(void)
-{
-}
-
-/* Create a simple special hash parameter. */
-
-/**/
-static Param
-createtihash()
-{
-    Param pm;
-    HashTable ht;
-
-    unsetparam(terminfo_nam);
-
-    if (!(pm = createparam(terminfo_nam, PM_SPECIAL|PM_HIDE|PM_HIDEVAL|
-			   PM_REMOVABLE|PM_HASHED)))
-	return NULL;
-
-    pm->level = pm->old ? locallevel : 0;
-    pm->gsu.h = &stdhash_gsu;
-    pm->u.hash = ht = newhashtable(7, terminfo_nam, NULL);
-
-    ht->hash        = hasher;
-    ht->emptytable  = (TableFunc) shempty;
-    ht->filltable   = NULL;
-    ht->addnode     = (AddNodeFunc) shempty;
-    ht->getnode     = ht->getnode2 = getterminfo;
-    ht->removenode  = (RemoveNodeFunc) shempty;
-    ht->disablenode = NULL;
-    ht->enablenode  = NULL;
-    ht->freenode    = (FreeNodeFunc) shempty;
-    ht->printnode   = printparamnode;
-    ht->scantab     = scanterminfo;
-
-    return (terminfo_pm = pm);
-}
-
-/**/
 static HashNode
 getterminfo(UNUSED(HashTable ht), char *name)
 {
@@ -339,9 +283,30 @@ scanterminfo(UNUSED(HashTable ht), ScanFunc func, int flags)
     }
 }
 
+static struct paramdef partab[] = {
+    SPECIALPMDEF("terminfo", PM_READONLY, NULL,
+		 getterminfo, scanterminfo)
+};
+
 /**/
 #endif /* USE_TERMINFO_MODULE */
 
+static struct features module_features = {
+#ifdef USE_TERMINFO_MODULE
+    bintab, sizeof(bintab)/sizeof(*bintab),
+#else
+    NULL, 0,
+#endif
+    NULL, 0,
+#ifdef USE_TERMINFO_MODULE
+    partab, sizeof(partab)/sizeof(*partab),
+#else
+    NULL, 0,
+#endif
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -351,6 +316,21 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
 #ifdef USE_TERMINFO_MODULE
@@ -360,30 +340,16 @@ boot_(Module m)
     if (setupterm((char *)0, 1, &errret) == ERR)
 	return 1;
 # endif
-
-    if (!createtihash())
-    	return 1;
-#else
-    unsetparam(terminfo_nam);
 #endif
-    return  !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-#ifdef USE_TERMINFO_MODULE
-    Param pm;
-
-    if ((pm = (Param) paramtab->getnode(paramtab, terminfo_nam)) &&
-	pm == terminfo_pm) {
-	pm->node.flags &= ~PM_READONLY;
-	unsetparam_pm(pm, 0, 1);
-    }
-#endif
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/zftp.c b/Src/Modules/zftp.c
index 8c9cdf735..89b869592 100644
--- a/Src/Modules/zftp.c
+++ b/Src/Modules/zftp.c
@@ -3151,7 +3151,6 @@ zftp_cleanup(void)
     zfunsetparam("ZFTP_SESSION");
     freelinklist(zfsessions, (FreeFunc) freesession);
     zfree(zfstatusp, sizeof(int)*zfsesscnt);
-    deletebuiltins("zftp", bintab, sizeof(bintab)/sizeof(*bintab));
 }
 
 static int
@@ -3161,47 +3160,68 @@ zftpexithook(UNUSED(Hookdef d), UNUSED(void *dummy))
     return 0;
 }
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /* The load/unload routines required by the zsh library interface */
 
 /**/
 int
 setup_(UNUSED(Module m))
 {
-    /* setup_ returns 0 for success. require_module returns 1 for success. */
-    return !require_module("", "zsh/net/tcp", 0, 0);
+    return (require_module("", "zsh/net/tcp", NULL) == 1);
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
 }
 
 /**/
 int
 boot_(UNUSED(Module m))
 {
-    int ret;
-    if ((ret = addbuiltins("zftp", bintab,
-			   sizeof(bintab)/sizeof(*bintab))) == 1) {
-	/* if successful, set some default parameters */
-	off_t tmout_def = 60;
-	zfsetparam("ZFTP_VERBOSE", ztrdup("450"), ZFPM_IFUNSET);
-	zfsetparam("ZFTP_TMOUT", &tmout_def, ZFPM_IFUNSET|ZFPM_INTEGER);
-	zfsetparam("ZFTP_PREFS", ztrdup("PS"), ZFPM_IFUNSET);
-	/* default preferences if user deletes variable */
-	zfprefs = ZFPF_SNDP|ZFPF_PASV;
+    /*
+     * Set some default parameters.
+     * These aren't special, so aren't associated with features.
+     */
+    off_t tmout_def = 60;
+    zfsetparam("ZFTP_VERBOSE", ztrdup("450"), ZFPM_IFUNSET);
+    zfsetparam("ZFTP_TMOUT", &tmout_def, ZFPM_IFUNSET|ZFPM_INTEGER);
+    zfsetparam("ZFTP_PREFS", ztrdup("PS"), ZFPM_IFUNSET);
+    /* default preferences if user deletes variable */
+    zfprefs = ZFPF_SNDP|ZFPF_PASV;
     
-	zfsessions = znewlinklist();
-	newsession("default");
+    zfsessions = znewlinklist();
+    newsession("default");
 
-	addhookfunc("exit", zftpexithook);
-    }
+    addhookfunc("exit", zftpexithook);
 
-    return !ret;
+    return 0;
 }
 
 /**/
 int
-cleanup_(UNUSED(Module m))
+cleanup_(Module m)
 {
     deletehookfunc("exit", zftpexithook);
     zftp_cleanup();
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/zprof.c b/Src/Modules/zprof.c
index ca053a9e9..b30e44432 100644
--- a/Src/Modules/zprof.c
+++ b/Src/Modules/zprof.c
@@ -295,6 +295,14 @@ static struct funcwrap wrapper[] = {
     WRAPDEF(zprof_wrapper),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(Module m)
@@ -305,6 +313,21 @@ setup_(Module m)
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     calls = NULL;
@@ -312,8 +335,7 @@ boot_(Module m)
     arcs = NULL;
     narcs = 0;
     stack = NULL;
-    return !(addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) |
-	     !addwrapper(m, wrapper));
+    return addwrapper(m, wrapper);
 }
 
 /**/
@@ -322,9 +344,8 @@ cleanup_(Module m)
 {
     freepfuncs(calls);
     freeparcs(arcs);
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     deletewrapper(m, wrapper);
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/zpty.c b/Src/Modules/zpty.c
index 744548f50..3280b8175 100644
--- a/Src/Modules/zpty.c
+++ b/Src/Modules/zpty.c
@@ -725,10 +725,20 @@ ptyhook(UNUSED(Hookdef d), UNUSED(void *dummy))
     return 0;
 }
 
+
 static struct builtin bintab[] = {
     BUILTIN("zpty", 0, bin_zpty, 0, -1, 0, "ebdrwLnt", NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -738,12 +748,27 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     ptycmds = NULL;
 
     addhookfunc("exit", ptyhook);
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
 }
 
 /**/
@@ -752,8 +777,7 @@ cleanup_(Module m)
 {
     deletehookfunc("exit", ptyhook);
     deleteallptycmds();
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Modules/zselect.c b/Src/Modules/zselect.c
index 4e547a47e..3e71fa3bd 100644
--- a/Src/Modules/zselect.c
+++ b/Src/Modules/zselect.c
@@ -267,10 +267,20 @@ bin_zselect(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 #endif
 }
 
+
 static struct builtin bintab[] = {
     BUILTIN("zselect", 0, bin_zselect, 0, -1, 0, NULL, NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
+
 /* The load/unload routines required by the zsh library interface */
 
 /**/
@@ -282,20 +292,34 @@ setup_(UNUSED(Module m))
 
 /**/
 int
-boot_(Module m)
+features_(Module m, char ***features)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
 }
 
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
 
 /**/
 int
-cleanup_(UNUSED(Module m))
+boot_(Module m)
 {
-    deletebuiltins("zselect", bintab, sizeof(bintab)/sizeof(*bintab));
     return 0;
 }
 
+
+/**/
+int
+cleanup_(Module m)
+{
+    return setfeatureenables(m->nam, &module_features, NULL);
+}
+
 /**/
 int
 finish_(UNUSED(Module m))
diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c
index 1e4bf975a..a4172c581 100644
--- a/Src/Modules/zutil.c
+++ b/Src/Modules/zutil.c
@@ -1740,6 +1740,13 @@ static struct builtin bintab[] = {
     BUILTIN("zparseopts", 0, bin_zparseopts, 1, -1, 0, NULL, NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
 
 /**/
 int
@@ -1752,17 +1759,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Zle/compctl.c b/Src/Zle/compctl.c
index 81f11b423..bafbb2f68 100644
--- a/Src/Zle/compctl.c
+++ b/Src/Zle/compctl.c
@@ -3942,6 +3942,14 @@ static struct builtin bintab[] = {
     BUILTIN("compcall", 0, bin_compcall, 0, 0, 0, "TD", NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -3964,11 +3972,26 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     addhookfunc("compctl_make", (Hookfn) ccmakehookfn);
     addhookfunc("compctl_cleanup", (Hookfn) cccleanuphookfn);
-    return (addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) != 1);
+    return 0;
 }
 
 /**/
@@ -3977,8 +4000,7 @@ cleanup_(Module m)
 {
     deletehookfunc("compctl_make", (Hookfn) ccmakehookfn);
     deletehookfunc("compctl_cleanup", (Hookfn) cccleanuphookfn);
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c
index a73d3cf14..aac760a92 100644
--- a/Src/Zle/complete.c
+++ b/Src/Zle/complete.c
@@ -1467,6 +1467,14 @@ struct hookdef comphooks[] = {
     HOOKDEF("comp_list_matches", ilistmatches, 0),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    cotab, sizeof(cotab)/sizeof(*cotab),
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -1492,6 +1500,21 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     addhookfunc("complete", (Hookfn) do_completion);
@@ -1502,11 +1525,7 @@ boot_(Module m)
     addhookfunc("list_matches", (Hookfn) list_matches);
     addhookfunc("invalidate_list", (Hookfn) invalidate_list);
     addhookdefs(m->nam, comphooks, sizeof(comphooks)/sizeof(*comphooks));
-    if (!(addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)) |
-	  addconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab)) |
-	  !addwrapper(m, wrapper)))
-	return 1;
-    return 0;
+    return addwrapper(m, wrapper);
 }
 
 /**/
@@ -1521,10 +1540,8 @@ cleanup_(Module m)
     deletehookfunc("list_matches", (Hookfn) list_matches);
     deletehookfunc("invalidate_list", (Hookfn) invalidate_list);
     deletehookdefs(m->nam, comphooks, sizeof(comphooks)/sizeof(*comphooks));
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    deleteconddefs(m->nam, cotab, sizeof(cotab)/sizeof(*cotab));
     deletewrapper(m, wrapper);
-    return 0;
+    return seteatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Zle/complist.c b/Src/Zle/complist.c
index cedccc254..787b7b25c 100644
--- a/Src/Zle/complist.c
+++ b/Src/Zle/complist.c
@@ -3219,6 +3219,14 @@ menuselect(char **args)
     return 0;
 }
 
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -3228,6 +3236,21 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     mtab = NULL;
@@ -3269,7 +3292,7 @@ boot_(Module m)
 
 /**/
 int
-cleanup_(UNUSED(Module m))
+cleanup_(Module m)
 {
     free(mtab);
     free(mgtab);
@@ -3279,7 +3302,7 @@ cleanup_(UNUSED(Module m))
     deletehookfunc("menu_start", (Hookfn) domenuselect);
     unlinkkeymap("menuselect", 1);
     unlinkkeymap("listscroll", 1);
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Zle/computil.c b/Src/Zle/computil.c
index dbbaa61e2..0d8cf5364 100644
--- a/Src/Zle/computil.c
+++ b/Src/Zle/computil.c
@@ -4567,6 +4567,14 @@ static struct builtin bintab[] = {
     BUILTIN("compgroups", 0, bin_compgroups, 1, -1, 0, NULL, NULL),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 
 /**/
 int
@@ -4584,17 +4592,31 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
-    return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
+    return 0;
 }
 
 /**/
 int
 cleanup_(Module m)
 {
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Zle/deltochar.c b/Src/Zle/deltochar.c
index e7bfabfd3..0c64cf18d 100644
--- a/Src/Zle/deltochar.c
+++ b/Src/Zle/deltochar.c
@@ -74,6 +74,16 @@ deltochar(UNUSED(char **args))
     return !ok;
 }
 
+
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -83,6 +93,21 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     w_deletetochar = addzlefunction("delete-to-char", deltochar,
@@ -100,11 +125,11 @@ boot_(Module m)
 
 /**/
 int
-cleanup_(UNUSED(Module m))
+cleanup_(Module m)
 {
     deletezlefunction(w_deletetochar);
     deletezlefunction(w_zaptochar);
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Zle/zle_main.c b/Src/Zle/zle_main.c
index 9f71f692b..8ab10e5de 100644
--- a/Src/Zle/zle_main.c
+++ b/Src/Zle/zle_main.c
@@ -1777,6 +1777,14 @@ mod_export struct hookdef zlehooks[] = {
     HOOKDEF("invalidate_list", NULL, 0),
 };
 
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    NULL, 0,
+    0
+};
+
 /**/
 int
 setup_(UNUSED(Module m))
@@ -1817,11 +1825,25 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m->nam, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
 boot_(Module m)
 {
     addhookfunc("before_trap", (Hookfn) zlebeforetrap);
     addhookfunc("after_trap", (Hookfn) zleaftertrap);
-    addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     addhookdefs(m->nam, zlehooks, sizeof(zlehooks)/sizeof(*zlehooks));
     return 0;
 }
@@ -1836,9 +1858,8 @@ cleanup_(Module m)
     }
     deletehookfunc("before_trap", (Hookfn) zlebeforetrap);
     deletehookfunc("after_trap", (Hookfn) zleaftertrap);
-    deletebuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab));
     deletehookdefs(m->nam, zlehooks, sizeof(zlehooks)/sizeof(*zlehooks));
-    return 0;
+    return setfeatureenables(m->nam, &module_features, NULL);
 }
 
 /**/
diff --git a/Src/Zle/zle_thingy.c b/Src/Zle/zle_thingy.c
index debd31a28..42e32f142 100644
--- a/Src/Zle/zle_thingy.c
+++ b/Src/Zle/zle_thingy.c
@@ -590,7 +590,7 @@ bin_zle_complete(char *name, char **args, UNUSED(Options ops), UNUSED(char func)
     Thingy t;
     Widget w, cw;
 
-    if (!require_module(name, "zsh/complete", 0, 0)) {
+    if (require_module(name, "zsh/complete", NULL) == 1) {
 	zwarnnam(name, "can't load complete module");
 	return 1;
     }
diff --git a/Src/Zle/zle_tricky.c b/Src/Zle/zle_tricky.c
index 6aaf53e80..2b8e17c22 100644
--- a/Src/Zle/zle_tricky.c
+++ b/Src/Zle/zle_tricky.c
@@ -612,7 +612,7 @@ docomplete(int lst)
      * no completion widgets are defined. */
 
     if (!module_loaded("zsh/compctl") && !hascompwidgets)
-	load_module("zsh/compctl");
+	(void)load_module("zsh/compctl", NULL);
 
     if (runhookdef(BEFORECOMPLETEHOOK, (void *) &lst)) {
 	active = 0;
diff --git a/Src/Zle/zleparameter.c b/Src/Zle/zleparameter.c
index 3c7f1474f..1b84fdff7 100644
--- a/Src/Zle/zleparameter.c
+++ b/Src/Zle/zleparameter.c
@@ -30,45 +30,6 @@
 #include "zleparameter.mdh"
 #include "zleparameter.pro"
 
-/* Empty dummy function for special hash parameters. */
-
-/**/
-static void
-shempty(void)
-{
-}
-
-/* Create a simple special hash parameter. */
-
-/**/
-static Param
-createspecialhash(char *name, GetNodeFunc get, ScanTabFunc scan)
-{
-    Param pm;
-    HashTable ht;
-
-    if (!(pm = createparam(name, PM_SPECIAL|PM_HIDE|PM_REMOVABLE|PM_HASHED)))
-	return NULL;
-
-    pm->level = pm->old ? locallevel : 0;
-    pm->gsu.h = &stdhash_gsu;
-    pm->u.hash = ht = newhashtable(0, name, NULL);
-
-    ht->hash        = hasher;
-    ht->emptytable  = (TableFunc) shempty;
-    ht->filltable   = NULL;
-    ht->addnode     = (AddNodeFunc) shempty;
-    ht->getnode     = ht->getnode2 = get;
-    ht->removenode  = (RemoveNodeFunc) shempty;
-    ht->disablenode = NULL;
-    ht->enablenode  = NULL;
-    ht->freenode    = (FreeNodeFunc) shempty;
-    ht->printnode   = printparamnode;
-    ht->scantab     = scan;
-
-    return pm;
-}
-
 /* Functions for the zlewidgets special parameter. */
 
 /**/
@@ -157,18 +118,6 @@ keymapsgetfn(UNUSED(Param pm))
     return ret;
 }
 
-/* Table for defined parameters. */
-
-struct pardef {
-    char *name;
-    int flags;
-    GetNodeFunc getnfn;
-    ScanTabFunc scantfn;
-    GsuHash hash_gsu;
-    GsuArray array_gsu;
-    Param pm;
-};
-
 /*
  * This is a duplicate of stdhash_gsu.  On some systems
  * (such as Cygwin) we can't put a pointer to an imported variable
@@ -179,14 +128,18 @@ static const struct gsu_hash zlestdhash_gsu =
 static const struct gsu_array keymaps_gsu =
 { keymapsgetfn, arrsetfn, stdunsetfn };
 
-static struct pardef partab[] = {
-    { "widgets", PM_READONLY,
-      getpmwidgets, scanpmwidgets, &zlestdhash_gsu,
-      NULL, NULL },
-    { "keymaps", PM_ARRAY|PM_SPECIAL|PM_READONLY,
-      NULL, NULL, NULL,
-      &keymaps_gsu, NULL },
-    { NULL, 0, NULL, NULL, NULL, NULL, NULL }
+static struct paramdef partab[] = {
+    SPECIALPMDEF("widgets", PM_READONLY,
+		 &zlestdhash_gsu, getpmwidgets, scanpmwidgets),
+    SPECIALPMDEF("keymaps", PM_ARRAY|PM_READONLY, &keymaps_gsu, NULL, NULL),
+};
+
+static struct features module_features = {
+    NULL, 0,
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    NULL, 0,
+    0
 };
 
 /**/
@@ -198,48 +151,35 @@ setup_(UNUSED(Module m))
 
 /**/
 int
-boot_(UNUSED(Module m))
+features_(Module m, char ***features)
 {
-    struct pardef *def;
-
-    for (def = partab; def->name; def++) {
-	unsetparam(def->name);
-
-	if (def->getnfn) {
-	    if (!(def->pm = createspecialhash(def->name, def->getnfn,
-					      def->scantfn)))
-		return 1;
-	    def->pm->node.flags |= def->flags;
-	    if (def->hash_gsu)
-		def->pm->gsu.h = def->hash_gsu;
-	} else {
-	    if (!(def->pm = createparam(def->name, def->flags | PM_HIDE)))
-		return 1;
-	    def->pm->gsu.a = def->array_gsu;
-	}
-    }
+    *features = featuresarray(m->nam, &module_features);
     return 0;
 }
 
 /**/
 int
-cleanup_(UNUSED(Module m))
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m->nam, &module_features, enables);
+}
+
+/**/
+int
+boot_(UNUSED(Module m))
 {
-    Param pm;
-    struct pardef *def;
-
-    for (def = partab; def->name; def++) {
-	if ((pm = (Param) paramtab->getnode(paramtab, def->name)) &&
-	    pm == def->pm) {
-	    pm->node.flags &= ~PM_READONLY;
-	    unsetparam_pm(pm, 0, 1);
-	}
-    }
     return 0;
 }
 
 /**/
 int
+cleanup_(Module m)
+{
+    return setfeatureenables(m->nam, &module_features, NULL);
+}
+
+/**/
+int
 finish_(UNUSED(Module m))
 {
     return 0;
diff --git a/Src/builtin.c b/Src/builtin.c
index d8732af45..17af59398 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -131,7 +131,7 @@ static struct builtin builtins[] =
     BUILTIN("whence", 0, bin_whence, 0, -1, 0, "acmpvfsw", NULL),
     BUILTIN("where", 0, bin_whence, 0, -1, 0, "pmsw", "ca"),
     BUILTIN("which", 0, bin_whence, 0, -1, 0, "ampsw", "c"),
-    BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "ARILabcfdipue", NULL),
+    BUILTIN("zmodload", 0, bin_zmodload, 0, -1, 0, "AFRILP:abcfdilpue", NULL),
     BUILTIN("zcompile", 0, bin_zcompile, 0, -1, 0, "tUMRcmzka", NULL),
 };
 
@@ -2033,6 +2033,10 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 	 * because we've checked for unpleasant surprises above.
 	 */
 	pm->node.flags = (PM_TYPE(pm->node.flags) | on | PM_SPECIAL) & ~off;
+	/*
+	 * Readonlyness of special parameters must be preserved.
+	 */
+	pm->node.flags |= tpm->node.flags & PM_READONLY;
 	if (newspecial == NS_SECONDS) {
 	    /* We save off the raw internal value of the SECONDS var */
 	    tpm->u.dval = getrawseconds();
@@ -2085,7 +2089,21 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 		    "%s: array elements must be scalar", pname);
 	    return NULL;
 	}
-    } else if (isident(pname) && !idigit(*pname)) {
+    } 
+    /*
+     * As we can hide existing parameters, we allow a name if
+     * it's not a normal identifier but is one of the special
+     * set found in the parameter table.  The second test is
+     * because we can set individual positional parameters;
+     * however "0" is not a positional parameter and is OK.
+     *
+     * It would be neater to extend isident() and be clearer
+     * about where we allow various parameter types.  It's
+     * not entirely clear to me isident() should reject
+     * specially named parameters given that it accepts digits.
+     */
+    else if ((isident(pname) || paramtab->getnode(paramtab, pname))
+	     && (!idigit(*pname) || !strcmp(pname, "0"))) {
 	/*
 	 * Create a new node for a parameter with the flags in `on' minus the
 	 * readonly flag
@@ -2101,10 +2119,10 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 		return NULL;
 	}
     } else {
-	if (isident(pname))
-	    zerrnam(cname, "not valid in this context: %s", pname);
-	else
+	if (idigit(*pname))
 	    zerrnam(cname, "not an identifier: %s", pname);
+	else
+	    zerrnam(cname, "not valid in this context: %s", pname);
 	return NULL;
     }
 
@@ -2138,7 +2156,8 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
 		  "BUG: parameter recreated with wrong flags");
 	    unsetparam_pm(ipm, 0, 1);
 	}
-    } else if (newspecial != NS_NONE && !(pm->old->node.flags & PM_NORESTORE)) {
+    } else if (newspecial != NS_NONE &&
+	       !(pm->old->node.flags & (PM_NORESTORE|PM_READONLY))) {
 	/*
 	 * We need to use the special setting function to re-initialise
 	 * the special parameter to empty.
diff --git a/Src/cond.c b/Src/cond.c
index 8950d845c..a597587b6 100644
--- a/Src/cond.c
+++ b/Src/cond.c
@@ -95,7 +95,10 @@ evalcond(Estate state, char *fromtest)
     case COND_REGEX:
 	{
 	    char *modname = isset(REMATCHPCRE) ? "zsh/pcre" : "zsh/regex";
-	    if (!load_module_silence(modname, 1)) {
+	    /*
+	     * TODO: we just need to load the appropriate condition.
+	     */
+	    if (load_module_silence(modname, NULL, 1) == 1) {
 		zwarnnam(fromtest, "%s not available for regex",
 			 modname);
 		return 2;
diff --git a/Src/exec.c b/Src/exec.c
index f711f3d30..751282127 100644
--- a/Src/exec.c
+++ b/Src/exec.c
@@ -2012,7 +2012,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 
 		/* autoload the builtin if necessary */
 		if (!((Builtin) hn)->handlerfunc) {
-		    load_module(((Builtin) hn)->optstr);
+		    (void)load_module(((Builtin) hn)->optstr, NULL);
 		    hn = builtintab->getnode(builtintab, cmdarg);
 		}
 		assign = (hn && (hn->flags & BINF_MAGICEQUALS));
@@ -2229,7 +2229,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
 
 		/* autoload the builtin if necessary */
 		if (!((Builtin) hn)->handlerfunc) {
-		    load_module(((Builtin) hn)->optstr);
+		    (void)load_module(((Builtin) hn)->optstr, NULL);
 		    hn = builtintab->getnode(builtintab, cmdarg);
 		}
 		break;
diff --git a/Src/init.c b/Src/init.c
index 6c5421e6f..fce8adcd5 100644
--- a/Src/init.c
+++ b/Src/init.c
@@ -979,7 +979,7 @@ run_init_scripts(void)
 		 * Always attempt to load the newuser module to perform
 		 * checks for new zsh users.  Don't care if we can't load it.
 		 */
-		if (load_module_silence("zsh/newuser", 1)) {
+		if (!load_module_silence("zsh/newuser", NULL, 1)) {
 		    /* Unload it immediately. */
 		    unload_named_module("zsh/newuser", "zsh", 1);
 		}
@@ -1152,7 +1152,7 @@ init_bltinmods(void)
 
 #include "bltinmods.list"
 
-    load_module("zsh/main");
+    (void)load_module("zsh/main", NULL);
 }
 
 /**/
@@ -1213,8 +1213,8 @@ char *
 autoload_zleread(char **lp, char **rp, int ha, int con)
 {
     zlereadptr = fallback_zleread;
-    if (load_module("zsh/zle"))
-	load_module("zsh/compctl");
+    if (load_module("zsh/zle", NULL) != 1)
+	(void)load_module("zsh/compctl", NULL);
     return zlereadptr(lp, rp, ha, con);
 }
 
@@ -1240,7 +1240,7 @@ static void
 autoload_zlesetkeymap(int mode)
 {
     zlesetkeymapptr = noop_function_int;
-    load_module("zsh/zle");
+    (void)load_module("zsh/zle", NULL);
     (*zlesetkeymapptr)(mode);
 }
 
diff --git a/Src/mkbltnmlst.sh b/Src/mkbltnmlst.sh
index 3af496f07..a3785cb8c 100644
--- a/Src/mkbltnmlst.sh
+++ b/Src/mkbltnmlst.sh
@@ -87,11 +87,16 @@ for bin_mod in $bin_mods; do
     echo "    {"
     echo "        extern int setup_${q_bin_mod} _((Module));"
     echo "        extern int boot_${q_bin_mod} _((Module));"
+    echo "        extern int features_${q_bin_mod} _((Module,char***));"
+    echo "        extern int enables_${q_bin_mod} _((Module,int**));"
     echo "        extern int cleanup_${q_bin_mod} _((Module));"
     echo "        extern int finish_${q_bin_mod} _((Module));"
     echo
     echo "        register_module(\"$bin_mod\","
-    echo "                        setup_${q_bin_mod}, boot_${q_bin_mod},"
+    echo "                        setup_${q_bin_mod},"
+    echo "                        features_${q_bin_mod},"
+    echo "                        enables_${q_bin_mod},"
+    echo "                        boot_${q_bin_mod},"
     echo "                        cleanup_${q_bin_mod}, finish_${q_bin_mod});"
     echo "    }"
     done_mods="$done_mods$bin_mod "
diff --git a/Src/mkmakemod.sh b/Src/mkmakemod.sh
index da01c953b..d275038d9 100644
--- a/Src/mkmakemod.sh
+++ b/Src/mkmakemod.sh
@@ -364,6 +364,8 @@ if $first_stage; then
 	echo "	    fi; \\"
 	echo "	    echo '#   define boot_ boot_${q_name}'; \\"
 	echo "	    echo '#   define cleanup_ cleanup_${q_name}'; \\"
+	echo "	    echo '#   define features_ features_${q_name}'; \\"
+	echo "	    echo '#   define enables_ enables_${q_name}'; \\"
 	echo "	    echo '#   define setup_ setup_${q_name}'; \\"
 	echo "	    echo '#   define finish_ finish_${q_name}'; \\"
 	echo "	    if test @SHORTBOOTNAMES@ = yes; then \\"
diff --git a/Src/modentry.c b/Src/modentry.c
index 87c89e0d7..ea2ca6656 100644
--- a/Src/modentry.c
+++ b/Src/modentry.c
@@ -8,7 +8,7 @@ int modentry _((int boot, Module m));
 
 /**/
 int
-modentry(int boot, Module m)
+modentry(int boot, Module m, void *ptr)
 {
     switch (boot) {
     case 0:
@@ -27,6 +27,14 @@ modentry(int boot, Module m)
 	return finish_(m);
 	break;
 
+    case 4:
+	return features_(m, (char ***)ptr);
+	break;
+
+    case 5:
+	return enables_(m, (int **)ptr);
+	break;
+
     default:
 	zerr("bad call to modentry");
 	return 1;
diff --git a/Src/module.c b/Src/module.c
index ffa659efc..8f5afca83 100644
--- a/Src/module.c
+++ b/Src/module.c
@@ -35,6 +35,20 @@
 /**/
 LinkList linkedmodules;
 
+/* $module_path ($MODULE_PATH) */
+
+/**/
+char **module_path;
+
+/* List of modules */
+
+/**/
+mod_export LinkList modules;
+
+
+/************************************************************************
+ * zsh/main standard module functions
+ ************************************************************************/
 
 /* The `zsh/main' module contains all the base code that can't actually be *
  * built as a separate module.  It is initialised by main(), so there's    *
@@ -49,6 +63,24 @@ setup_(UNUSED(Module m))
 
 /**/
 int
+features_(UNUSED(Module m), UNUSED(char ***features))
+{
+    /*
+     * There are lots and lots of features, but they're not
+     * handled here.
+     */
+    return 1;
+}
+
+/**/
+int
+enables_(UNUSED(Module m), UNUSED(int **enables))
+{
+    return 1;
+}
+
+/**/
+int
 boot_(UNUSED(Module m))
 {
     return 0;
@@ -68,12 +100,21 @@ finish_(UNUSED(Module m))
     return 0;
 }
 
+
+/************************************************************************
+ * Module utility functions
+ ************************************************************************/
+
 /* This registers a builtin module.                                   */
 
 /**/
 void
-register_module(char *n, Module_func setup, Module_func boot,
-		Module_func cleanup, Module_func finish)
+register_module(char *n, Module_void_func setup,
+		Module_features_func features,
+		Module_enables_func enables,
+		Module_void_func boot,
+		Module_void_func cleanup,
+		Module_void_func finish)
 {
     Linkedmod m;
 
@@ -81,6 +122,8 @@ register_module(char *n, Module_func setup, Module_func boot,
 
     m->name = ztrdup(n);
     m->setup = setup;
+    m->features = features;
+    m->enables = enables;
     m->boot = boot;
     m->cleanup = cleanup;
     m->finish = finish;
@@ -124,6 +167,12 @@ module_linked(char const *name)
     return NULL;
 }
 
+
+/************************************************************************
+ * Support for the various feature types.
+ * First, builtins.
+ ************************************************************************/
+
 /* addbuiltin() can be used to add a new builtin.  It returns zero on *
  * success, 1 on failure.  The only possible type of failure is that  *
  * a builtin with the specified name already exists.  An autoloaded   *
@@ -142,13 +191,81 @@ addbuiltin(Builtin b)
     return 0;
 }
 
-/* Add multiple builtins.  binl points to a table of `size' builtin      *
- * structures.  Those for which (.flags & BINF_ADDED) is false are to be *
- * added; that flag is set if they succeed.  If any fail, an error       *
- * message is printed, using nam as the leading name.  Returns 1 if all  *
- * additions succeed, 2 if some succeed and some fail, and 0 if all (and *
- * at least 1) fail.  The usual usage in a boot_*() function would be    *
- *  return !addbuiltins(m->nam, bintab, sizeof(bintab)/sizeof(*bintab)); */
+/* Define an autoloadable builtin.  It returns 0 on success, or 1 on *
+ * failure.  The only possible cause of failure is that a builtin    *
+ * with the specified name already exists.                           */
+
+/**/
+int
+add_autobin(char *nam, char *module)
+{
+    Builtin bn = zshcalloc(sizeof(*bn));
+    bn->node.nam = ztrdup(nam);
+    bn->optstr = ztrdup(module);
+    return addbuiltin(bn);
+}
+
+/* Remove the builtin added previously by addbuiltin().  Returns *
+ * zero on succes and -1 if there is no builtin with that name.  */
+
+/**/
+int
+deletebuiltin(char *nam)
+{
+    Builtin bn;
+
+    bn = (Builtin) builtintab->removenode(builtintab, nam);
+    if (!bn)
+	return -1;
+    builtintab->freenode(&bn->node);
+    return 0;
+}
+
+/**/
+mod_export int
+setbuiltins(char const *nam, Builtin binl, int size, int *e)
+{
+    int hads = 0, hadf = 0, n;
+
+    for(n = 0; n < size; n++) {
+	Builtin b = &binl[n];
+	if (e && *e++) {
+	    if (b->node.flags & BINF_ADDED)
+		continue;
+	    if (addbuiltin(b)) {
+		zwarnnam(nam,
+			 "name clash when adding builtin `%s'", b->node.nam);
+		hadf = 1;
+	    } else {
+		b->node.flags |= BINF_ADDED;
+		hads = 2;
+	    }
+	} else {
+	    if (!(b->node.flags & BINF_ADDED))
+		continue;
+	    if (deletebuiltin(b->node.nam)) {
+		zwarnnam(nam, "builtin `%s' already deleted", b->node.nam);
+		hadf = 1;
+	    } else {
+		hads = 2;
+		b->node.flags &= ~BINF_ADDED;
+	    }
+	}
+    }
+    return hadf ? hads : 1;
+}
+
+/*
+ * Add multiple builtins.  binl points to a table of `size' builtin
+ * structures.  Those for which (.flags & BINF_ADDED) is false are to be
+ * added; that flag is set if they succeed.
+ *
+ * If any fail, an error message is printed, using nam as the leading name.
+ * Returns 1 if all additions succeed, 2 if some succeed and some fail, and 0
+ * if all (and at least 1) fail.
+ *
+ * This should not be used from a module; instead, use handlefeatures().
+ */
 
 /**/
 mod_export int
@@ -171,6 +288,11 @@ addbuiltins(char const *nam, Builtin binl, int size)
     return hadf ? hads : 1;
 }
 
+
+/************************************************************************
+ * Function wrappers.
+ ************************************************************************/
+
 /* The list of function wrappers defined. */
 
 /**/
@@ -208,104 +330,713 @@ addwrapper(Module m, FuncWrap w)
     return 0;
 }
 
-/* $module_path ($MODULE_PATH) */
+/* This removes the given wrapper definition from the list. Returned is *
+ * one in case of error and zero otherwise. */
 
 /**/
-char **module_path;
+mod_export int
+deletewrapper(Module m, FuncWrap w)
+{
+    FuncWrap p, q;
 
-/* List of modules */
+    if (m->flags & MOD_ALIAS)
+	return 1;
+
+    if (w->flags & WRAPF_ADDED) {
+	for (p = wrappers, q = NULL; p && p != w; q = p, p = p->next);
+
+	if (p) {
+	    if (q)
+		q->next = p->next;
+	    else
+		wrappers = p->next;
+	    p->flags &= ~WRAPF_ADDED;
+
+	    return 0;
+	}
+    }
+    return 1;
+}
+
+
+/************************************************************************
+ * Conditions.
+ ************************************************************************/
+
+/* The list of module-defined conditions. */
 
 /**/
-mod_export LinkList modules;
+mod_export Conddef condtab;
 
-/* Define an autoloadable builtin.  It returns 0 on success, or 1 on *
- * failure.  The only possible cause of failure is that a builtin    *
- * with the specified name already exists.                           */
+/* This gets a condition definition with the given name. The first        *
+ * argument says if we have to look for an infix condition. The last      *
+ * argument is non-zero if we should autoload modules if needed. */
+
+/**/
+Conddef
+getconddef(int inf, char *name, int autol)
+{
+    Conddef p;
+    int f = 1;
+
+    do {
+	for (p = condtab; p; p = p->next) {
+	    if ((!!inf == !!(p->flags & CONDF_INFIX)) &&
+		!strcmp(name, p->name))
+		break;
+	}
+	if (autol && p && p->module) {
+	    /* This is a definition for an autoloaded condition, load the *
+	     * module if we haven't tried that already. */
+	    if (f) {
+		(void)load_module_silence(p->module, NULL, 0);
+		f = 0;
+		p = NULL;
+	    } else {
+		deleteconddef(p);
+		return NULL;
+	    }
+	} else
+	    break;
+    } while (!p);
+    return p;
+}
+
+/*
+ * This adds the given condition definition. The return value is zero on *
+ * success and 1 on failure. If there is a matching definition for an    *
+ * autoloaded condition, it is removed.
+ *
+ * This is used for adding both an autoload definition or
+ * a real condition.  In the latter case the caller is responsible
+ * for setting the CONDF_ADDED flag.
+ */
+
+/**/
+static int
+addconddef(Conddef c)
+{
+    Conddef p = getconddef((c->flags & CONDF_INFIX), c->name, 0);
+
+    if (p) {
+	if (!p->module || (p->flags & CONDF_ADDED))
+	    return 1;
+	/* There is an autoload definition. */
+
+	deleteconddef(p);
+    }
+    c->next = condtab;
+    condtab = c;
+    return 0;
+}
+
+/* This removes the given condition definition from the list(s). If this *
+ * is a definition for a autoloaded condition, the memory is freed. */
 
 /**/
 int
-add_autobin(char *nam, char *module)
+deleteconddef(Conddef c)
 {
-    Builtin bn = zshcalloc(sizeof(*bn));
-    bn->node.nam = ztrdup(nam);
-    bn->optstr = ztrdup(module);
-    return addbuiltin(bn);
+    Conddef p, q;
+
+    for (p = condtab, q = NULL; p && p != c; q = p, p = p->next);
+
+    if (p) {
+	if (q)
+	    q->next = p->next;
+	else 
+	    condtab = p->next;
+		
+	if (p->module) {
+	    /* autoloaded, free it */
+	    zsfree(p->name);
+	    zsfree(p->module);
+	    zfree(p, sizeof(*p));
+	}
+	return 0;
+    }
+    return -1;
 }
 
-/* Remove the builtin added previously by addbuiltin().  Returns *
- * zero on succes and -1 if there is no builtin with that name.  */
+/**/
+mod_export int
+setconddefs(char const *nam, Conddef c, int size, int *e)
+{
+    int hads = 0, hadf = 0;
+
+    while (size--) {
+	if (e && *e++) {
+	    if (c->flags & CONDF_ADDED) {
+		c++;
+		continue;
+	    }
+	    if (addconddef(c)) {
+		zwarnnam(nam, "name clash when adding condition `%s'",
+			 c->name);
+		hadf = 1;
+	    } else {
+		c->flags |= CONDF_ADDED;
+		hads = 2;
+	    }
+	} else {
+	    if (!(c->flags & CONDF_ADDED)) {
+		c++;
+		continue;
+	    }
+	    if (deleteconddef(c)) {
+		zwarnnam(nam, "condition `%s' already deleted", c->name);
+		hadf = 1;
+	    } else {
+		c->flags &= ~CONDF_ADDED;
+		hads = 2;
+	    }
+	}
+	c++;
+    }
+    return hadf ? hads : 1;
+}
+
+/* This adds a definition for autoloading a module for a condition. */
 
 /**/
 int
-deletebuiltin(char *nam)
+add_autocond(char *nam, int inf, char *module)
 {
-    Builtin bn;
+    Conddef c = (Conddef) zalloc(sizeof(*c));
 
-    bn = (Builtin) builtintab->removenode(builtintab, nam);
-    if (!bn)
-	return -1;
-    builtintab->freenode(&bn->node);
+    c->name = ztrdup(nam);
+    c->flags = (inf  ? CONDF_INFIX : 0);
+    c->module = ztrdup(module);
+
+    if (addconddef(c)) {
+	zsfree(c->name);
+	zsfree(c->module);
+	zfree(c, sizeof(*c));
+
+	return 1;
+    }
     return 0;
 }
 
-/* Delete multiple builtins.  binl points to a table of `size' builtin  *
- * structures.  Those for which (.flags & BINF_ADDED) is true are to be *
- * deleted; that flag is cleared.  If any fail, an error message is     *
- * printed, using nam as the leading name.  Returns 1 if all deletions  *
- * succeed, 2 if some succeed and some fail, and 0 if all (and at least *
- * 1) fail.  In normal use, from a cleanup_*() function, this return    *
- * value would be ignored -- the only cause of failure would be that a  *
- * wayward module had deleted our builtin without telling us.           */
+
+/************************************************************************
+ * Hook functions.
+ ************************************************************************/
+
+/* This list of hook functions defined. */
+
+/**/
+Hookdef hooktab;
+
+/* Find a hook definition given the name. */
+
+/**/
+Hookdef
+gethookdef(char *n)
+{
+    Hookdef p;
+
+    for (p = hooktab; p; p = p->next)
+	if (!strcmp(n, p->name))
+	    return p;
+    return NULL;
+}
+
+/* This adds the given hook definition. The return value is zero on      *
+ * success and 1 on failure.                                             */
+
+/**/
+int
+addhookdef(Hookdef h)
+{
+    if (gethookdef(h->name))
+	return 1;
+
+    h->next = hooktab;
+    hooktab = h;
+    h->funcs = znewlinklist();
+
+    return 0;
+}
+
+/* This adds multiple hook definitions. This is like addbuiltins(). */
 
 /**/
 mod_export int
-deletebuiltins(char const *nam, Builtin binl, int size)
+addhookdefs(char const *nam, Hookdef h, int size)
 {
-    int hads = 0, hadf = 0, n;
+    int hads = 0, hadf = 0;
 
-    for(n = 0; n < size; n++) {
-	Builtin b = &binl[n];
-	if(!(b->node.flags & BINF_ADDED))
-	    continue;
-	if(deletebuiltin(b->node.nam)) {
-	    zwarnnam(nam, "builtin `%s' already deleted", b->node.nam);
+    while (size--) {
+	if (addhookdef(h)) {
+	    zwarnnam(nam, "name clash when adding hook `%s'", h->name);
 	    hadf = 1;
 	} else
 	    hads = 2;
-	b->node.flags &= ~BINF_ADDED;
+	h++;
     }
     return hadf ? hads : 1;
 }
 
-/* This removes the given wrapper definition from the list. Returned is *
- * one in case of error and zero otherwise. */
+/* Delete hook definitions. */
 
 /**/
-mod_export int
-deletewrapper(Module m, FuncWrap w)
+int
+deletehookdef(Hookdef h)
 {
-    FuncWrap p, q;
+    Hookdef p, q;
 
-    if (m->flags & MOD_ALIAS)
+    for (p = hooktab, q = NULL; p && p != h; q = p, p = p->next);
+
+    if (!p)
 	return 1;
 
-    if (w->flags & WRAPF_ADDED) {
-	for (p = wrappers, q = NULL; p && p != w; q = p, p = p->next);
+    if (q)
+	q->next = p->next;
+    else
+	hooktab = p->next;
+    freelinklist(p->funcs, NULL);
+    return 0;
+}
 
-	if (p) {
-	    if (q)
-		q->next = p->next;
-	    else
-		wrappers = p->next;
-	    p->flags &= ~WRAPF_ADDED;
+/**/
+mod_export int
+deletehookdefs(UNUSED(char const *nam), Hookdef h, int size)
+{
+    while (size--) {
+	deletehookdef(h);
+	h++;
+    }
+    return 1;
+}
 
+/* Add a function to a hook. */
+
+/**/
+int
+addhookdeffunc(Hookdef h, Hookfn f)
+{
+    zaddlinknode(h->funcs, (void *) f);
+
+    return 0;
+}
+
+/**/
+mod_export int
+addhookfunc(char *n, Hookfn f)
+{
+    Hookdef h = gethookdef(n);
+
+    if (h)
+	return addhookdeffunc(h, f);
+    return 1;
+}
+
+/* Delete a function from a hook. */
+
+/**/
+int
+deletehookdeffunc(Hookdef h, Hookfn f)
+{
+    LinkNode p;
+
+    for (p = firstnode(h->funcs); p; incnode(p))
+	if (f == (Hookfn) getdata(p)) {
+	    remnode(h->funcs, p);
 	    return 0;
 	}
-    }
     return 1;
 }
 
 /**/
+mod_export int
+deletehookfunc(char *n, Hookfn f)
+{
+    Hookdef h = gethookdef(n);
+
+    if (h)
+	return deletehookdeffunc(h, f);
+    return 1;
+}
+
+/* Run the function(s) for a hook. */
+
+/**/
+mod_export int
+runhookdef(Hookdef h, void *d)
+{
+    if (empty(h->funcs)) {
+	if (h->def)
+	    return h->def(h, d);
+	return 0;
+    } else if (h->flags & HOOKF_ALL) {
+	LinkNode p;
+	int r;
+
+	for (p = firstnode(h->funcs); p; incnode(p))
+	    if ((r = ((Hookfn) getdata(p))(h, d)))
+		return r;
+	if (h->def)
+	    return h->def(h, d);
+	return 0;
+    } else
+	return ((Hookfn) getdata(lastnode(h->funcs)))(h, d);
+}
+
+/**/
+int
+runhook(char *n, void *d)
+{
+    Hookdef h = gethookdef(n);
+
+    if (h)
+	return runhookdef(h, d);
+    return 0;
+}
+
+
+/************************************************************************
+ * Shell parameters.
+ ************************************************************************/
+
+static int
+checkaddparam(char *nam)
+{
+    Param pm;
+
+    if (!(pm = (Param) gethashnode2(paramtab, nam)))
+	return 0;
+
+    if (pm->level || !(pm->node.flags & PM_AUTOLOAD)) {
+	zwarn("Can't add module parameter `%s': %s",
+	      nam, pm->level ?
+	      "local parameter exists" :
+	      "parameter already exists");
+	return 1;
+    }
+
+    unsetparam_pm(pm, 0, 1);
+    return 0;
+}
+
+/* This adds the given parameter definition. The return value is zero on *
+ * success and 1 on failure. */
+
+/**/
+int
+addparamdef(Paramdef d)
+{
+    Param pm;
+
+    if (checkaddparam(d->name))
+	return 1;
+
+    if (d->getnfn) {
+	if (!(pm = createspecialhash(d->name, d->getnfn,
+				     d->scantfn, d->flags)))
+	    return 1;
+    }
+    else if (!(pm = createparam(d->name, d->flags)) &&
+	!(pm = (Param) paramtab->getnode(paramtab, d->name)))
+	return 1;
+
+    d->pm = pm;
+    pm->level = 0;
+    if (d->var)
+	pm->u.data = d->var;
+    if (d->var || d->gsu) {
+	/*
+	 * If no get/set/unset class, use the appropriate
+	 * variable type, else use the one supplied.
+	 */
+	switch (PM_TYPE(pm->node.flags)) {
+	case PM_SCALAR:
+	    pm->gsu.s = d->gsu ? (GsuScalar)d->gsu : &varscalar_gsu;
+	    break;
+
+	case PM_INTEGER:
+	    pm->gsu.i = d->gsu ? (GsuInteger)d->gsu : &varinteger_gsu;
+	    break;
+
+	case PM_ARRAY:
+	    pm->gsu.a = d->gsu ? (GsuArray)d->gsu : &vararray_gsu;
+	    break;
+
+	case PM_HASHED:
+	    /* hashes may behave like standard hashes */
+	    if (d->gsu)
+		pm->gsu.h = (GsuHash)d->gsu;
+	    break;
+
+	default:
+	    unsetparam_pm(pm, 0, 1);
+	    return 1;
+	}
+    }
+
+    return 0;
+}
+
+/* Delete parameters defined. No error checking yet. */
+
+/**/
+int
+deleteparamdef(Paramdef d)
+{
+    Param pm = (Param) paramtab->getnode(paramtab, d->name);
+
+    if (!pm)
+	return 1;
+    if (pm != d->pm) {
+	/*
+	 * See if the parameter has been hidden.  If so,
+	 * bring it to the front to unset it.
+	 */
+	Param prevpm, searchpm;
+	for (prevpm = pm, searchpm = pm->old;
+	     searchpm;
+	     prevpm = searchpm, searchpm = searchpm->old)
+	    if (searchpm == d->pm)
+		break;
+
+	if (!searchpm)
+	    return 1;
+
+	paramtab->removenode(paramtab, pm->node.nam);
+	prevpm->old = searchpm->old;
+	searchpm->old = pm;
+	paramtab->addnode(paramtab, searchpm->node.nam, searchpm);
+
+	pm = searchpm;
+    }
+    pm->node.flags = (pm->node.flags & ~PM_READONLY) | PM_REMOVABLE;
+    unsetparam_pm(pm, 0, 1);
+    d->pm = NULL;
+    return 0;
+}
+
+/**/
+mod_export int
+setparamdefs(char const *nam, Paramdef d, int size, int *e)
+{
+    int hads = 0, hadf = 0;
+
+    while (size--) {
+	if (e && *e++) {
+	    if (d->pm) {
+		d++;
+		continue;
+	    }
+	    if (addparamdef(d)) {
+		zwarnnam(nam, "error when adding parameter `%s'", d->name);
+		hadf = 1;
+	    } else {
+		hads = 2;
+	    }
+	} else {
+	    if (!d->pm) {
+		d++;
+		continue;
+	    }
+	    if (deleteparamdef(d)) {
+		zwarnnam(nam, "parameter `%s' already deleted", d->name);
+		hadf = 1;
+	    } else {
+		hads = 2;
+	    }
+	}
+	d++;
+    }
+    return hadf ? hads : 1;
+}
+
+/* This adds a definition for autoloading a module for a parameter. */
+
+/**/
+void
+add_autoparam(char *nam, char *module)
+{
+    Param pm;
+
+    queue_signals();
+    if (checkaddparam(nam)) {
+	unqueue_signals();
+	return;
+    }
+
+    pm = setsparam(nam, ztrdup(module));
+
+    pm->node.flags |= PM_AUTOLOAD;
+    unqueue_signals();
+}
+
+
+/************************************************************************
+ * Math functions.
+ ************************************************************************/
+
+/* List of math functions. */
+
+/**/
+MathFunc mathfuncs;
+
+/**/
+void
+removemathfunc(MathFunc previous, MathFunc current)
+{
+    if (previous)
+	previous->next = current->next;
+    else
+	mathfuncs = current->next;
+
+    zsfree(current->name);
+    zsfree(current->module);
+    zfree(current, sizeof(*current));
+}
+
+/**/
+MathFunc
+getmathfunc(char *name, int autol)
+{
+    MathFunc p, q = NULL;
+
+    for (p = mathfuncs; p; q = p, p = p->next)
+	if (!strcmp(name, p->name)) {
+	    if (autol && p->module && !(p->flags & MFF_USERFUNC)) {
+		char *n = dupstring(p->module);
+
+		removemathfunc(q, p);
+
+		(void)load_module_silence(n, NULL, 0);
+
+		return getmathfunc(name, 0);
+	    }
+	    return p;
+	}
+
+    return NULL;
+}
+
+/**/
+static int
+addmathfunc(MathFunc f)
+{
+    MathFunc p, q = NULL;
+
+    if (f->flags & MFF_ADDED)
+	return 1;
+
+    for (p = mathfuncs; p; q = p, p = p->next)
+	if (!strcmp(f->name, p->name)) {
+	    if (p->module && !(p->flags & MFF_USERFUNC)) {
+		/*
+		 * Autoloadable, replace.
+		 */
+		removemathfunc(q, p);
+		break;
+	    }
+	    return 1;
+	}
+
+    f->next = mathfuncs;
+    mathfuncs = f;
+
+    return 0;
+}
+
+/**/
+mod_export int
+deletemathfunc(MathFunc f)
+{
+    MathFunc p, q;
+
+    for (p = mathfuncs, q = NULL; p && p != f; q = p, p = p->next);
+
+    if (p) {
+	if (q)
+	    q->next = f->next;
+	else
+	    mathfuncs = f->next;
+
+	/* the following applies to both unloaded and user-defined functions */
+	if (f->module) {
+	    zsfree(f->name);
+	    zsfree(f->module);
+	    zfree(f, sizeof(*f));
+	} else
+	    f->flags &= ~MFF_ADDED;
+
+	return 0;
+    }
+    return -1;
+}
+
+/**/
+mod_export int
+setmathfuncs(char const *nam, MathFunc f, int size, int *e)
+{
+    int hads = 0, hadf = 0;
+
+    while (size--) {
+	if (e && *e++) {
+	    if (f->flags & MFF_ADDED) {
+		f++;
+		continue;
+	    }
+	    if (addmathfunc(f)) {
+		zwarnnam(nam, "name clash when adding math function `%s'",
+			 f->name);
+		hadf = 1;
+	    } else {
+		f->flags |= MFF_ADDED;
+		hads = 2;
+	    }
+	} else {
+	    if (!(f->flags & MFF_ADDED)) {
+		f++;
+		continue;
+	    }
+	    if (deletemathfunc(f)) {
+		zwarnnam(nam, "math function `%s' already deleted", f->name);
+		hadf = 1;
+	    } else {
+		f->flags &= ~MFF_ADDED;
+		hads = 2;
+	    }	    
+	}
+	f++;
+    }
+    return hadf ? hads : 1;
+}
+
+/**/
+int
+add_automathfunc(char *nam, char *module)
+{
+    MathFunc f = (MathFunc) zalloc(sizeof(*f));
+
+    f->name = ztrdup(nam);
+    f->module = ztrdup(module);
+    f->flags = 0;
+
+    if (addmathfunc(f)) {
+	zsfree(f->name);
+	zsfree(f->module);
+	zfree(f, sizeof(*f));
+
+	return 1;
+    }
+
+    return 0;
+}
+
+
+/************************************************************************
+ * Now support for dynamical loading and the fallback functions
+ * we use for loading if dynamical loading is not available.
+ ************************************************************************/
+
+/**/
 #ifdef DYNAMIC
 
 /**/
@@ -392,11 +1123,15 @@ hpux_dlsym(void *handle, char *name)
 
 #ifdef DLSYM_NEEDS_UNDERSCORE
 # define STR_SETUP     "_setup_"
+# define STR_FEATURES  "_features_"
+# define STR_ENABLES   "_enables_"
 # define STR_BOOT      "_boot_"
 # define STR_CLEANUP   "_cleanup_"
 # define STR_FINISH    "_finish_"
 #else /* !DLSYM_NEEDS_UNDERSCORE */
 # define STR_SETUP     "setup_"
+# define STR_FEATURES  "features_"
+# define STR_ENABLES   "enables_"
 # define STR_BOOT      "boot_"
 # define STR_CLEANUP   "cleanup_"
 # define STR_FINISH    "finish_"
@@ -542,38 +1277,52 @@ module_loaded(const char *name)
 static int
 dyn_setup_module(Module m)
 {
-    return ((int (*)_((int,Module))) m->u.handle)(0, m);
+    return ((int (*)_((int,Module))) m->u.handle)(0, m, NULL);
+}
+
+/**/
+static int
+dyn_features_module(Module m, char ***features)
+{
+    return ((int (*)_((int,Module))) m->u.handle)(4, m, features);
+}
+
+/**/
+static int
+dyn_enables_module(Module m, int **enables)
+{
+    return ((int (*)_((int,Module))) m->u.handle)(5, m, enables);
 }
 
 /**/
 static int
 dyn_boot_module(Module m)
 {
-    return ((int (*)_((int,Module))) m->u.handle)(1, m);
+    return ((int (*)_((int,Module))) m->u.handle)(1, m, NULL);
 }
 
 /**/
 static int
 dyn_cleanup_module(Module m)
 {
-    return ((int (*)_((int,Module))) m->u.handle)(2, m);
+    return ((int (*)_((int,Module))) m->u.handle)(2, m, NULL);
 }
 
 /**/
 static int
 dyn_finish_module(Module m)
 {
-    return ((int (*)_((int,Module))) m->u.handle)(3, m);
+    return ((int (*)_((int,Module))) m->u.handle)(3, m, NULL);
 }
 
 /**/
 #else
 
-static Module_func
+static Module_generic_func
 module_func(Module m, char *name)
 {
 #ifdef DYNAMIC_NAME_CLASH_OK
-    return (Module_func) dlsym(m->u.handle, name);
+    return (Module_generic_func) dlsym(m->u.handle, name);
 #else /* !DYNAMIC_NAME_CLASH_OK */
     VARARR(char, buf, strlen(name) + strlen(m->nam)*2 + 1);
     char const *p;
@@ -594,7 +1343,7 @@ module_func(Module m, char *name)
 	    *q++ = *p;
     }
     *q = 0;
-    return (Module_func) dlsym(m->u.handle, buf);
+    return (Module_generic_func) dlsym(m->u.handle, buf);
 #endif /* !DYNAMIC_NAME_CLASH_OK */
 }
 
@@ -602,7 +1351,7 @@ module_func(Module m, char *name)
 static int
 dyn_setup_module(Module m)
 {
-    Module_func fn = module_func(m, STR_SETUP);
+    Module_void_func fn = (Module_void_func)module_func(m, STR_SETUP);
 
     if (fn)
 	return fn(m);
@@ -612,9 +1361,34 @@ dyn_setup_module(Module m)
 
 /**/
 static int
+dyn_features_module(Module m, char ***features)
+{
+    Module_features_func fn =
+	(Module_features_func)module_func(m, STR_FEATURES);
+
+    if (fn)
+	return fn(m, features);
+    /* not a user-visible error if no features function */
+    return 1;
+}
+
+/**/
+static int
+dyn_enables_module(Module m, int **enables)
+{
+    Module_enables_func fn = (Module_enables_func)module_func(m, STR_ENABLES);
+
+    if (fn)
+	return fn(m, enables);
+    /* not a user-visible error if no enables function */
+    return 1;
+}
+
+/**/
+static int
 dyn_boot_module(Module m)
 {
-    Module_func fn = module_func(m, STR_BOOT);
+    Module_void_func fn = (Module_void_func)module_func(m, STR_BOOT);
 
     if(fn)
 	return fn(m);
@@ -626,7 +1400,7 @@ dyn_boot_module(Module m)
 static int
 dyn_cleanup_module(Module m)
 {
-    Module_func fn = module_func(m, STR_CLEANUP);
+    Module_void_func fn = (Module_void_func)module_func(m, STR_CLEANUP);
 
     if(fn)
 	return fn(m);
@@ -641,7 +1415,7 @@ dyn_cleanup_module(Module m)
 static int
 dyn_finish_module(Module m)
 {
-    Module_func fn = module_func(m, STR_FINISH);
+    Module_void_func fn = (Module_void_func)module_func(m, STR_FINISH);
     int r;
 
     if (fn)
@@ -667,6 +1441,24 @@ setup_module(Module m)
 
 /**/
 static int
+features_module(Module m, char ***features)
+{
+    return ((m->flags & MOD_LINKED) ?
+	    (m->u.linked->features)(m, features) :
+	    dyn_features_module(m, features));
+}
+
+/**/
+static int
+enables_module(Module m, int **enables)
+{
+    return ((m->flags & MOD_LINKED) ?
+	    (m->u.linked->enables)(m, enables) :
+	    dyn_enables_module(m, enables));
+}
+
+/**/
+static int
 boot_module(Module m)
 {
     return ((m->flags & MOD_LINKED) ?
@@ -701,6 +1493,22 @@ setup_module(Module m)
 
 /**/
 static int
+features_module(Module m, char ***features)
+{
+    return ((m->flags & MOD_LINKED) ? (m->u.linked->features)(m, features)
+	    : 1);
+}
+
+/**/
+static int
+enables_module(Module m, int **enables)
+{
+    return ((m->flags & MOD_LINKED) ? (m->u.linked->enables)(m, enables)
+	    : 1);
+}
+
+/**/
+static int
 boot_module(Module m)
 {
     return ((m->flags & MOD_LINKED) ? (m->u.linked->boot)(m) : 1);
@@ -723,6 +1531,125 @@ finish_module(Module m)
 /**/
 #endif /* !DYNAMIC */
 
+
+/************************************************************************
+ * Functions called when manipulating modules
+ ************************************************************************/
+
+/*
+ * Set the features for the module, which must be loaded
+ * by now (though may not be fully set up).
+ *
+ * Return 0 for success, 1 for failure, 2 if some features
+ * couldn't be set.
+ */
+
+/**/
+static int
+do_module_features(Module m, char **enablesstr, int silent)
+{
+    char **features;
+
+    if (features_module(m, &features) == 0) {
+	/*
+	 * Features are supported.  If we were passed
+	 * a NULL array, enable all features, else
+	 * enable only the features listed.
+	 * (This may in principle be an empty array,
+	 * although that's not very pointful.)
+	 */
+	int *enables = NULL;
+	if (enables_module(m, &enables)) {
+	    /* If features are supported, enables should be, too */
+	    if (!silent)
+		zwarn("error getting enabled features for module `%s'",
+		      m->nam);
+	    return 1;
+	}
+
+	if (enablesstr) {
+	    char **ep;
+	    for (ep = enablesstr; *ep; ep++) {
+		char **fp, *esp = *ep;
+		int on = 1;
+		if (*esp == '+')
+		    esp++;
+		else if (*esp == '-') {
+		    on = 0;
+		    esp++;
+		}
+		for (fp = features; *fp; fp++)
+		    if (!strcmp(*fp, esp)) {
+			enables[fp - features] = on;
+			break;
+		    }
+		if (!*fp) {
+		    if (!silent)
+			zwarn("module `%s' has no such feature: %s",
+			      m->nam, esp);
+		    return 1;
+		}
+	    }
+	} else {
+	    /*
+	     * Enable all features.  This is used when loading
+	     * without using zmodload -F.
+	     */
+	    int n_features = arrlen(features);
+	    int *ep;
+	    for (ep = enables; n_features--; ep++)
+		*ep = 1;
+	}
+
+	if (enables_module(m, &enables))
+	    return 2;
+    } else if (enablesstr) {
+	if (!silent)
+	    zwarn("module `%s' does not support features", m->nam);
+	return 1;
+    }
+    /* Else it doesn't support features but we don't care. */
+
+    return 0;
+}
+
+/*
+ * Boot the module, including setting up features.
+ * As we've only just loaded the module, we don't yet
+ * know what features it supports, so we get them passed
+ * as a string.
+ *
+ * Returns 0 if OK, 1 if completely failed, 2 if some features
+ * couldn't be set up.
+ */
+
+/**/
+static int
+do_boot_module(Module m, char **enablesstr, int silent)
+{
+    int ret = do_module_features(m, enablesstr, silent);
+
+    if (ret == 1)
+	return 1;
+
+    if (boot_module(m))
+	return 1;
+    return ret;
+}
+
+/*
+ * Cleanup the module.
+ */
+
+/**/
+static int
+do_cleanup_module(Module m)
+{
+    return (m->flags & MOD_LINKED) ?
+	(m->u.linked && m->u.linked->cleanup(m)) :
+	(m->u.handle && cleanup_module(m));
+}
+
 /**/
 static int
 modname_ok(char const *p)
@@ -735,27 +1662,37 @@ modname_ok(char const *p)
     return 0;
 }
 
+/*
+ * Now returns 0 for success (changed post-4.3.4),
+ * 1 for complete failure, 2 if some features couldn't be set.
+ */
+
 /**/
 mod_export int
-load_module(char const *name)
+load_module(char const *name, char **enablesstr)
 {
-    return load_module_silence(name, 0);
+    return load_module_silence(name, enablesstr, 0);
 }
 
+/*
+ * Returns 0 for success (changed post-4.3.4), 1 for complete
+ * failure, 2 if some features couldn't be set.
+ */
+
 /**/
 mod_export int
-load_module_silence(char const *name, int silent)
+load_module_silence(char const *name, char **enablesstr, int silent)
 {
     Module m;
     void *handle = NULL;
     Linkedmod linked;
     LinkNode node, n;
-    int set;
+    int set, bootret;
 
     if (!modname_ok(name)) {
 	if (!silent)
 	    zerr("invalid module name `%s'", name);
-	return 0;
+	return 1;
     }
     /*
      * The following function call may alter name to the final name in a
@@ -767,7 +1704,7 @@ load_module_silence(char const *name, int silent)
 	if (!(linked = module_linked(name)) &&
 	    !(handle = do_load_module(name, silent))) {
 	    unqueue_signals();
-	    return 0;
+	    return 1;
 	}
 	m = zshcalloc(sizeof(*m));
 	m->nam = ztrdup(name);
@@ -780,40 +1717,47 @@ load_module_silence(char const *name, int silent)
 	}
 	node = zaddlinknode(modules, m);
 
-	if ((set = setup_module(m)) || boot_module(m)) {
-	    if (!set)
+	if ((set = setup_module(m)) ||
+	    (bootret = do_boot_module(m, enablesstr, silent)) == 1) {
+	    if (!set) {
+		do_cleanup_module(m);
 		finish_module(m);
+	    }
 	    delete_module(node);
 	    unqueue_signals();
-	    return 0;
+	    return 1;
 	}
 	m->flags |= MOD_INIT_S | MOD_INIT_B;
 	m->flags &= ~MOD_SETUP;
 	unqueue_signals();
-	return 1;
-    } 
+	return bootret;
+    }
     m = (Module) getdata(node);
     if (m->flags & MOD_SETUP) {
 	unqueue_signals();
-	return 1;
+	return 0;
     }
     if (m->flags & MOD_UNLOAD)
 	m->flags &= ~MOD_UNLOAD;
     else if ((m->flags & MOD_LINKED) ? m->u.linked : m->u.handle) {
 	unqueue_signals();
-	return 1;
+	return 0;
     }
     if (m->flags & MOD_BUSY) {
 	zerr("circular dependencies for module %s", name);
-	return 0;
+	return 1;
     }
     m->flags |= MOD_BUSY;
+    /*
+     * TODO: shouldn't we unload the module if one of
+     * its dependencies fails?
+     */
     if (m->deps)
 	for (n = firstnode(m->deps); n; incnode(n))
-	    if (!load_module_silence((char *) getdata(n), silent)) {
+	    if (load_module_silence((char *) getdata(n), NULL, silent) == 1) {
 		m->flags &= ~MOD_BUSY;
 		unqueue_signals();
-		return 0;
+		return 1;
 	    }
     m->flags &= ~MOD_BUSY;
     if (!m->u.handle) {
@@ -821,7 +1765,7 @@ load_module_silence(char const *name, int silent)
 	if (!(linked = module_linked(name)) &&
 	    !(handle = do_load_module(name, silent))) {
 	    unqueue_signals();
-	    return 0;
+	    return 1;
 	}
 	if (handle) {
 	    m->u.handle = handle;
@@ -837,12 +1781,13 @@ load_module_silence(char const *name, int silent)
 		m->u.linked = NULL;
 	    m->flags &= ~MOD_SETUP;
 	    unqueue_signals();
-	    return 0;
+	    return 1;
 	}
 	m->flags |= MOD_INIT_S;
     }
     m->flags |= MOD_SETUP;
-    if (boot_module(m)) {
+    if ((bootret = do_boot_module(m, enablesstr, silent)) == 1) {
+	do_cleanup_module(m);
 	finish_module(m);
 	if (m->flags & MOD_LINKED)
 	    m->u.linked = NULL;
@@ -850,42 +1795,44 @@ load_module_silence(char const *name, int silent)
 	    m->u.handle = NULL;
 	m->flags &= ~MOD_SETUP;
 	unqueue_signals();
-	return 0;
+	return 1;
     }
     m->flags |= MOD_INIT_B;
     m->flags &= ~MOD_SETUP;
     unqueue_signals();
-    return 1;
+    return bootret;
 }
 
 /* This ensures that the module with the name given as the second argument
  * is loaded.
- * The third argument should be non-zero if the function should complain
- * about trying to load a module with a full path name in restricted mode.
- * The last argument should be non-zero if this function should signal an
- * error if the module is already loaded.
- * The return value is non-zero if the module was found or loaded. */
+ * The last argument is the array of features to set.  If this is NULL
+ * and the module needs to be loaded, all features are enabled.
+ * If this is non-NULL the module features are set accordingly
+ * whether or not the module is loaded; it is an error if the
+ * module does not support the features passed (even if the feature
+ * is to be turned off) or if the module does not support features
+ * at all.
+ * The return value is 0 if the module was found or loaded
+ * (this changed post-4.3.4, because I got so confused---pws),
+ * 1 if loading failed completely, 2 if some features couldn't be set.
+ */
 
 /**/
 mod_export int
-require_module(char *nam, const char *module, UNUSED(int res), int test)
+require_module(char *nam, const char *module, char **features)
 {
     Module m = NULL;
     LinkNode node;
-    int ret = 1;
+    int ret = 0;
 
     /* Resolve aliases and actual loadable module as for load_module */
     queue_signals();
     node = find_module(module, 1, &module);
-    if (node && (m = ((Module) getdata(node)))->u.handle &&
-	!(m->flags & MOD_UNLOAD)) {
-	if (test) {
-	    unqueue_signals();
-	    zwarnnam(nam, "module %s already loaded.", module);
-	    return 0;
-	}
-    } else
-	ret = load_module_silence(module, 0);
+    if (!node || !(m = ((Module) getdata(node)))->u.handle ||
+	(m->flags & MOD_UNLOAD))
+	ret = load_module_silence(module, features, 0);
+    else
+	ret = do_module_features(m, features, 0);
     unqueue_signals();
 
     return ret;
@@ -953,6 +1900,11 @@ autoloadscan(HashNode hn, int printflags)
     putchar('\n');
 }
 
+
+/************************************************************************
+ * Handling for the zmodload builtin and its various options.
+ ************************************************************************/
+
 /**/
 int
 bin_zmodload(char *nam, char **args, Options ops, UNUSED(int func))
@@ -961,10 +1913,18 @@ bin_zmodload(char *nam, char **args, Options ops, UNUSED(int func))
 	OPT_ISSET(ops,'p') || OPT_ISSET(ops,'f');
     int ops_au = OPT_ISSET(ops,'a') || OPT_ISSET(ops,'u');
     int ret = 1;
+    /* options only allowed with -F */
+    char *fonly = "lP", *fp;
 
-    if (ops_bcpf && !ops_au) {
-	zwarnnam(nam, "-b, -c, -f, and -p must be combined with -a or -u");
-	return 1;
+    if (ops_bcpf) {
+	if (!ops_au) {
+	    zwarnnam(nam, "-b, -c, -f, and -p must be combined with -a or -u");
+	    return 1;
+	}
+	if (OPT_ISSET(ops,'F')) {
+	    zwarnnam(nam, "-b, -c, -f, and -p cannot be combined with -F");
+	    return 1;
+	}
     }
     if (OPT_ISSET(ops,'A') || OPT_ISSET(ops,'R')) {
 	if (ops_bcpf || ops_au || OPT_ISSET(ops,'d') || 
@@ -987,10 +1947,19 @@ bin_zmodload(char *nam, char **args, Options ops, UNUSED(int func))
 			       OPT_ISSET(ops,'a') || OPT_ISSET(ops,'d') ||
 			       OPT_ISSET(ops,'i') || OPT_ISSET(ops,'u'))) {
 	zwarnnam(nam, "-e cannot be combined with other options");
+	/* except -F ... */
 	return 1;
     }
+    for (fp = fonly; *fp; fp++) {
+	if (OPT_ISSET(ops,STOUC(*fp)) && !OPT_ISSET(ops,'F')) {
+	    zwarnnam(nam, "-%c is only allowed with -F", *fp);
+	    return 1;
+	}
+    }
     queue_signals();
-    if (OPT_ISSET(ops,'e'))
+    if (OPT_ISSET(ops, 'F'))
+	ret = bin_zmodload_features(nam, args, ops);
+    else if (OPT_ISSET(ops,'e'))
 	ret = bin_zmodload_exist(nam, args, ops);
     else if (OPT_ISSET(ops,'d'))
 	ret = bin_zmodload_dep(nam, args, ops);
@@ -1471,9 +2440,7 @@ unload_module(Module m, LinkNode node)
     }
     if ((m->flags & MOD_INIT_S) &&
 	!(m->flags & MOD_UNLOAD) &&
-	((m->flags & MOD_LINKED) ?
-	 (m->u.linked && m->u.linked->cleanup(m)) :
-	 (m->u.handle && cleanup_module(m))))
+	do_cleanup_module(m))
 	return 1;
     else {
 	int del = (m->flags & MOD_UNLOAD);
@@ -1622,608 +2589,274 @@ bin_zmodload_load(char *nam, char **args, Options ops)
 	return 0;
     } else {
 	/* load modules */
-	for (; *args; args++)
-	    if (!require_module(nam, *args, 1, (!OPT_ISSET(ops,'i'))))
-		ret = 1;
-
-	return ret;
-    }
-}
-
-/* The list of module-defined conditions. */
-
-/**/
-mod_export Conddef condtab;
-
-/* This gets a condition definition with the given name. The first        *
- * argument says if we have to look for an infix condition. The last      *
- * argument is non-zero if we should autoload modules if needed. */
-
-/**/
-Conddef
-getconddef(int inf, char *name, int autol)
-{
-    Conddef p;
-    int f = 1;
-
-    do {
-	for (p = condtab; p; p = p->next) {
-	    if ((!!inf == !!(p->flags & CONDF_INFIX)) &&
-		!strcmp(name, p->name))
-		break;
-	}
-	if (autol && p && p->module) {
-	    /* This is a definition for an autoloaded condition, load the *
-	     * module if we haven't tried that already. */
-	    if (f) {
-		load_module_silence(p->module, 0);
-		f = 0;
-		p = NULL;
-	    } else {
-		deleteconddef(p);
-		return NULL;
-	    }
-	} else
-	    break;
-    } while (!p);
-    return p;
-}
-
-/* This adds the given condition definition. The return value is zero on *
- * success and 1 on failure. If there is a matching definition for an    *
- * autoloaded condition, it is removed. */
-
-/**/
-int
-addconddef(Conddef c)
-{
-    Conddef p = getconddef((c->flags & CONDF_INFIX), c->name, 0);
-
-    if (p) {
-	if (!p->module || (p->flags & CONDF_ADDED))
-	    return 1;
-	/* There is an autoload definition. */
-
-	deleteconddef(p);
-    }
-    c->next = condtab;
-    condtab = c;
-    return 0;
-}
-
-/* This adds multiple condition definitions. This is like addbuiltins(). */
-
-/**/
-mod_export int
-addconddefs(char const *nam, Conddef c, int size)
-{
-    int hads = 0, hadf = 0;
-
-    while (size--) {
-	if (c->flags & CONDF_ADDED) {
-	    c++;
-	    continue;
-	}
-	if (addconddef(c)) {
-	    zwarnnam(nam, "name clash when adding condition `%s'", c->name);
-	    hadf = 1;
-	} else {
-	    c->flags |= CONDF_ADDED;
-	    hads = 2;
+	for (; *args; args++) {
+	    int tmpret = require_module(nam, *args, NULL);
+	    if (tmpret && ret != 1)
+		ret = tmpret;
 	}
-	c++;
-    }
-    return hadf ? hads : 1;
-}
-
-/* This list of hook functions defined. */
 
-/**/
-Hookdef hooktab;
-
-/* Find a hook definition given the name. */
-
-/**/
-Hookdef
-gethookdef(char *n)
-{
-    Hookdef p;
-
-    for (p = hooktab; p; p = p->next)
-	if (!strcmp(n, p->name))
-	    return p;
-    return NULL;
-}
-
-/* This adds the given hook definition. The return value is zero on      *
- * success and 1 on failure.                                             */
-
-/**/
-int
-addhookdef(Hookdef h)
-{
-    if (gethookdef(h->name))
-	return 1;
-
-    h->next = hooktab;
-    hooktab = h;
-    h->funcs = znewlinklist();
-
-    return 0;
-}
-
-/* This adds multiple hook definitions. This is like addbuiltins(). */
-
-/**/
-mod_export int
-addhookdefs(char const *nam, Hookdef h, int size)
-{
-    int hads = 0, hadf = 0;
-
-    while (size--) {
-	if (addhookdef(h)) {
-	    zwarnnam(nam, "name clash when adding hook `%s'", h->name);
-	    hadf = 1;
-	} else
-	    hads = 2;
-	h++;
+	return ret;
     }
-    return hadf ? hads : 1;
 }
 
-/* Delete hook definitions. */
-
 /**/
-int
-deletehookdef(Hookdef h)
+static int
+bin_zmodload_features(char *nam, char **args, Options ops)
 {
-    Hookdef p, q;
+    char *modname = *args;
 
-    for (p = hooktab, q = NULL; p && p != h; q = p, p = p->next);
-
-    if (!p)
+    if (!modname) {
+	zwarnnam(nam, "-F requires a module name");
 	return 1;
-
-    if (q)
-	q->next = p->next;
-    else
-	hooktab = p->next;
-    freelinklist(p->funcs, NULL);
-    return 0;
-}
-
-/**/
-mod_export int
-deletehookdefs(UNUSED(char const *nam), Hookdef h, int size)
-{
-    while (size--) {
-	deletehookdef(h);
-	h++;
     }
-    return 1;
-}
-
-/* Add a function to a hook. */
-
-/**/
-int
-addhookdeffunc(Hookdef h, Hookfn f)
-{
-    zaddlinknode(h->funcs, (void *) f);
-
-    return 0;
-}
-
-/**/
-mod_export int
-addhookfunc(char *n, Hookfn f)
-{
-    Hookdef h = gethookdef(n);
-
-    if (h)
-	return addhookdeffunc(h, f);
-    return 1;
-}
-
-/* Delete a function from a hook. */
-
-/**/
-int
-deletehookdeffunc(Hookdef h, Hookfn f)
-{
-    LinkNode p;
-
-    for (p = firstnode(h->funcs); p; incnode(p))
-	if (f == (Hookfn) getdata(p)) {
-	    remnode(h->funcs, p);
-	    return 0;
-	}
-    return 1;
-}
-
-/**/
-mod_export int
-deletehookfunc(char *n, Hookfn f)
-{
-    Hookdef h = gethookdef(n);
-
-    if (h)
-	return deletehookdeffunc(h, f);
-    return 1;
-}
-
-/* Run the function(s) for a hook. */
-
-/**/
-mod_export int
-runhookdef(Hookdef h, void *d)
-{
-    if (empty(h->funcs)) {
-	if (h->def)
-	    return h->def(h, d);
-	return 0;
-    } else if (h->flags & HOOKF_ALL) {
-	LinkNode p;
-	int r;
-
-	for (p = firstnode(h->funcs); p; incnode(p))
-	    if ((r = ((Hookfn) getdata(p))(h, d)))
-		return r;
-	if (h->def)
-	    return h->def(h, d);
-	return 0;
-    } else
-	return ((Hookfn) getdata(lastnode(h->funcs)))(h, d);
-}
-
-/**/
-int
-runhook(char *n, void *d)
-{
-    Hookdef h = gethookdef(n);
-
-    if (h)
-	return runhookdef(h, d);
-    return 0;
-}
-
-/* This adds the given parameter definition. The return value is zero on *
- * success and 1 on failure. */
-
-/**/
-int
-addparamdef(Paramdef d)
-{
-    Param pm;
-
-    if ((pm = (Param) gethashnode2(paramtab, d->name)))
-	unsetparam_pm(pm, 0, 1);
-
-    if (!(pm = createparam(d->name, d->flags)) &&
-	!(pm = (Param) paramtab->getnode(paramtab, d->name)))
-	return 1;
+    args++;
 
-    pm->level = 0;
-    pm->u.data = d->var;
-    if (d->gsu)
-	pm->gsu.i = (GsuInteger) d->gsu;
-    else {
+    if (OPT_ISSET(ops,'l') || OPT_ISSET(ops,'L') || OPT_ISSET(ops,'e')) {
 	/*
-	 * If no get/set/unset class, use the appropriate
-	 * variable type.
+	 * With option 'l', list all features one per line with + or -.
+	 * With option 'L', list as zmodload statement showing
+	 * only options turned on.
+	 * With both options, list as zmodload showing options
+	 * to be turned both on and off.
 	 */
-	switch (PM_TYPE(pm->node.flags)) {
-	case PM_SCALAR:
-	    pm->gsu.s = &varscalar_gsu;
-	    break;
-
-	case PM_INTEGER:
-	    pm->gsu.i = &varinteger_gsu;
-	    break;
-
-	case PM_ARRAY:
-	    pm->gsu.a = &vararray_gsu;
-	    break;
-
-	default:
-	    unsetparam_pm(pm, 0, 1);
+	LinkNode node;
+	Module m = NULL;
+	char **features, **fp, **arrset = NULL, **arrp = NULL;
+	int *enables = NULL, *ep;
+	char *param = OPT_ARG_SAFE(ops,'P');
+
+	node = find_module(modname, 1, NULL);
+	if (node)
+	    m = ((Module) getdata(node));
+	if (!m || !m->u.handle || (m->flags & MOD_UNLOAD)) {
+	    if (!OPT_ISSET(ops,'e'))
+		zwarnnam(nam, "module `%s' is not yet loaded", modname);
 	    return 1;
 	}
-    }
-
-    return 0;
-}
-
-/* This adds multiple parameter definitions. This is like addbuiltins(). */
-
-/**/
-mod_export int
-addparamdefs(char const *nam, Paramdef d, int size)
-{
-    int hads = 0, hadf = 0;
-
-    while (size--) {
-	if (addparamdef(d)) {
-	    zwarnnam(nam, "error when adding parameter `%s'", d->name);
-	    hadf = 1;
-	} else
-	    hads = 2;
-	d++;
-    }
-    return hadf ? hads : 1;
-}
-
-/* Delete parameters defined. No error checking yet. */
-
-/**/
-int
-deleteparamdef(Paramdef d)
-{
-    unsetparam(d->name);
-    return 0;
-}
-
-/**/
-mod_export int
-deleteparamdefs(UNUSED(char const *nam), Paramdef d, int size)
-{
-    while (size--) {
-	deleteparamdef(d);
-	d++;
-    }
-    return 1;
-}
-
-/* This adds a definition for autoloading a module for a condition. */
-
-/**/
-int
-add_autocond(char *nam, int inf, char *module)
-{
-    Conddef c = (Conddef) zalloc(sizeof(*c));
-
-    c->name = ztrdup(nam);
-    c->flags = (inf  ? CONDF_INFIX : 0);
-    c->module = ztrdup(module);
-
-    if (addconddef(c)) {
-	zsfree(c->name);
-	zsfree(c->module);
-	zfree(c, sizeof(*c));
-
-	return 1;
-    }
-    return 0;
-}
-
-/* This removes the given condition definition from the list(s). If this *
- * is a definition for a autoloaded condition, the memory is freed. */
-
-/**/
-int
-deleteconddef(Conddef c)
-{
-    Conddef p, q;
-
-    for (p = condtab, q = NULL; p && p != c; q = p, p = p->next);
-
-    if (p) {
-	if (q)
-	    q->next = p->next;
-	else 
-	    condtab = p->next;
-		
-	if (p->module) {
-	    /* autoloaded, free it */
-	    zsfree(p->name);
-	    zsfree(p->module);
-	    zfree(p, sizeof(*p));
+	if (features_module(m, &features)) {
+	    if (!OPT_ISSET(ops,'e'))
+		zwarnnam(nam, "module `%s' does not support features", m->nam);
+	    return 1;
 	}
-	return 0;
-    }
-    return -1;
-}
-
-/* This removes multiple condition definitions (like deletebuiltins()). */
-
-/**/
-mod_export int
-deleteconddefs(char const *nam, Conddef c, int size)
-{
-    int hads = 0, hadf = 0;
-
-    while (size--) {
-	if (!(c->flags & CONDF_ADDED)) {
-	    c++;
-	    continue;
+	if (enables_module(m, &enables)) {
+	    /* this shouldn't ever happen, so don't silence this error */
+	    zwarnnam(nam, "error getting enabled features for module `%s'",
+		     m->nam);
+	    return 1;
 	}
-	if (deleteconddef(c)) {
-	    zwarnnam(nam, "condition `%s' already deleted", c->name);
-	    hadf = 1;
-	} else
-	    hads = 2;
-	c->flags &= ~CONDF_ADDED;
-	c++;
+	for (arrp = args; *arrp; arrp++) {
+	    char *arg = *arrp;
+	    int on;
+	    if (*arg == '-') {
+		on = 0;
+		arg++;
+	    } else if (*arg == '+') {
+		on = 1;
+		arg++;
+	    } else
+		on = -1;
+	    for (fp = features, ep = enables; *fp; fp++, ep++) {
+		if (!strcmp(arg, *fp)) {
+		    /* for -e, check given state, if any */
+		    if (OPT_ISSET(ops,'e') && on != -1 &&
+			on != (*ep & 1))
+			return 1;
+		    break;
+		}
+	    }
+	    if (!*fp) {
+		if (!OPT_ISSET(ops,'e'))
+		    zwarnnam(nam, "module `%s' has no such feature: %s",
+			     *arrp);
+		return 1;
+	    }
+	}
+	if (OPT_ISSET(ops,'e'))		/* yep, everything we want exists */
+	    return 0;
+	if (OPT_ISSET(ops,'P')) {
+	    int arrlen = 0;
+	    for (fp = features, ep = enables; *fp; fp++, ep++) {
+		if (OPT_ISSET(ops, 'L') && !OPT_ISSET(ops, 'l') &&
+		    !*ep)
+		    continue;
+		if (*args) {
+		    char **argp;
+		    for (argp = args; *argp; argp++) {
+			char *arg = *argp;
+			/* ignore +/- for consistency */
+			if (*arg == '+' || *arg == '-')
+			    arg++;
+			if (!strcmp(*fp, arg))
+			    break;
+		    }
+		    if (!*argp)
+			continue;
+		}
+		arrlen++;
+	    }
+	    arrp = arrset = zalloc(sizeof(char *) * (arrlen+1));
+	} else if (OPT_ISSET(ops, 'L'))
+	    printf("zmodload -F %s ", m->nam);
+	for (fp = features, ep = enables; *fp; fp++, ep++) {
+	    char *onoff;
+	    int term;
+	    if (*args) {
+		char **argp;
+		for (argp = args; *argp; argp++) {
+		    char *arg = *argp;
+		    if (*arg == '+' || *arg == '-')
+			arg++;
+		    if (!strcmp(*fp, *argp))
+			break;
+		}
+		if (!*argp)
+		    continue;
+	    }
+	    if (OPT_ISSET(ops, 'L') && !OPT_ISSET(ops, 'l')) {
+		if (!*ep)
+		    continue;
+		onoff = "";
+	    } else if (*ep) {
+		onoff = "+";
+	    } else {
+		onoff = "-";
+	    }
+	    if (param) {
+		*arrp++ = bicat(onoff, *fp);
+	    } else {
+		if (OPT_ISSET(ops, 'L') && fp[1]) {
+		    term = ' ';
+		} else {
+		    term = '\n';
+		}
+		printf("%s%s%c", onoff, *fp, term);
+	    }
+	}
+	if (param) {
+	    *arrp = NULL;
+	    if (!setaparam(param, arrset))
+		return 1;
+	}
+	return 0;
+    } else if (OPT_ISSET(ops,'P')) {
+	zwarnnam(nam, "-P can only be used with -l or -L");
+	return 1;
     }
-    return hadf ? hads : 1;
-}
-
-/* This adds a definition for autoloading a module for a parameter. */
 
-/**/
-void
-add_autoparam(char *nam, char *module)
-{
-    Param pm;
-
-    queue_signals();
-    if ((pm = (Param) gethashnode2(paramtab, nam)))
-	unsetparam_pm(pm, 0, 1);
-
-    pm = setsparam(nam, ztrdup(module));
-
-    pm->node.flags |= PM_AUTOLOAD;
-    unqueue_signals();
+    return require_module(nam, modname, args);
 }
 
-/* List of math functions. */
 
-/**/
-MathFunc mathfuncs;
+/************************************************************************
+ * Generic feature support.
+ * These functions are designed to be called by modules.
+ ************************************************************************/
 
-/**/
-void
-removemathfunc(MathFunc previous, MathFunc current)
-{
-    if (previous)
-	previous->next = current->next;
-    else
-	mathfuncs = current->next;
-
-    zsfree(current->name);
-    zsfree(current->module);
-    zfree(current, sizeof(*current));
-}
+/*
+ * Construct a features array out of the list of concrete
+ * features given, leaving space for any abstract features
+ * to be added by the module itself.
+ *
+ * Note the memory is from the heap.
+ */
 
 /**/
-MathFunc
-getmathfunc(char *name, int autol)
+mod_export char **
+featuresarray(char const *nam, Features f)
 {
-    MathFunc p, q = NULL;
-
-    for (p = mathfuncs; p; q = p, p = p->next)
-	if (!strcmp(name, p->name)) {
-	    if (autol && p->module && !(p->flags & MFF_USERFUNC)) {
-		char *n = dupstring(p->module);
-
-		removemathfunc(q, p);
-
-		load_module_silence(n, 0);
+    int bn_size = f->bn_size, cd_size = f->cd_size;
+    int pd_size = f->pd_size, mf_size = f->mf_size;
+    int features_size = bn_size + cd_size + pd_size + mf_size + f->n_abstract;
+    Builtin bnp = f->bn_list;
+    Conddef cdp = f->cd_list;
+    Paramdef pdp = f->pd_list;
+    MathFunc mfp = f->mf_list;
+    char **features = (char **)zhalloc((features_size + 1) * sizeof(char *));
+    char **featurep = features;
 
-		return getmathfunc(name, 0);
-	    }
-	    return p;
-	}
+    while (bn_size--)
+	*featurep++ = dyncat("b:", (bnp++)->node.nam);
+    while (cd_size--)
+	*featurep++ = dyncat("c:", (cdp++)->name);
+    while (pd_size--)
+	*featurep++ = dyncat("p:", (pdp++)->name);
+    while (mf_size--)
+	*featurep++ = dyncat("f:", (mfp++)->name);
 
-    return NULL;
+    features[features_size] = NULL;
+    return features;
 }
 
+/*
+ * Return the current set of enables for the features in a
+ * module using heap memory.  Leave space for abstract
+ * features.  The array is not zero terminated.
+ */
 /**/
-mod_export int
-addmathfunc(MathFunc f)
+mod_export int *
+getfeatureenables(char const *nam, Features f)
 {
-    MathFunc p, q = NULL;
-
-    if (f->flags & MFF_ADDED)
-	return 1;
-
-    for (p = mathfuncs; p; q = p, p = p->next)
-	if (!strcmp(f->name, p->name)) {
-	    if (p->module && !(p->flags & MFF_USERFUNC)) {
-		/*
-		 * Autoloadable, replace.
-		 */
-		removemathfunc(q, p);
-		break;
-	    }
-	    return 1;
-	}
+    int bn_size = f->bn_size, cd_size = f->cd_size;
+    int pd_size = f->pd_size, mf_size = f->mf_size;
+    int features_size = bn_size + cd_size + pd_size + mf_size + f->n_abstract;
+    Builtin bnp = f->bn_list;
+    Conddef cdp = f->cd_list;
+    Paramdef pdp = f->pd_list;
+    MathFunc mfp = f->mf_list;
+    int *enables = zhalloc(sizeof(int) * features_size);
+    int *enablep = enables;
 
-    f->flags |= MFF_ADDED;
-    f->next = mathfuncs;
-    mathfuncs = f;
+    while (bn_size--)
+	*enablep++ = ((bnp++)->node.flags & BINF_ADDED) ? 1 : 0;
+    while (cd_size--)
+	*enablep++ = ((cdp++)->flags & CONDF_ADDED) ? 1 : 0;
+    while (pd_size--)
+	*enablep++ = (pdp++)->pm ? 1 : 0;
+    while (mf_size--)
+	*enablep++ = ((mfp++)->flags & MFF_ADDED) ? 1 : 0;
 
-    return 0;
+    return enables;
 }
 
-/**/
-mod_export int
-addmathfuncs(char const *nam, MathFunc f, int size)
-{
-    int hads = 0, hadf = 0;
-
-    while (size--) {
-	if (f->flags & MFF_ADDED) {
-	    f++;
-	    continue;
-	}
-	if (addmathfunc(f)) {
-	    zwarnnam(nam, "name clash when adding math function `%s'",
-		     f->name);
-	    hadf = 1;
-	} else
-	    hads = 2;
-	f++;
-    }
-    return hadf ? hads : 1;
-}
-
-/**/
-int
-add_automathfunc(char *nam, char *module)
-{
-    MathFunc f = (MathFunc) zalloc(sizeof(*f));
-
-    f->name = ztrdup(nam);
-    f->module = ztrdup(module);
-    f->flags = 0;
-
-    if (addmathfunc(f)) {
-	zsfree(f->name);
-	zsfree(f->module);
-	zfree(f, sizeof(*f));
-
-	return 1;
-    }
-    f->flags &= ~MFF_ADDED; /* still to autoload, not added yet */
-
-    return 0;
-}
+/*
+ * Add or remove the concrete features passed in arguments,
+ * depending on the corresponding element of the array e.
+ * If e is NULL, disable everything.
+ * Return 0 for success, 1 for failure; does not attempt
+ * to imitate the return values of addbuiltins() etc.
+ * Any failure in adding a requested feature is an
+ * error.
+ */
 
 /**/
 mod_export int
-deletemathfunc(MathFunc f)
+setfeatureenables(char const *nam, Features f, int *e)
 {
-    MathFunc p, q;
-
-    for (p = mathfuncs, q = NULL; p && p != f; q = p, p = p->next);
-
-    if (p) {
-	if (q)
-	    q->next = f->next;
-	else
-	    mathfuncs = f->next;
-
-	/* the following applies to both unloaded and user-defined functions */
-	if (f->module) {
-	    zsfree(f->name);
-	    zsfree(f->module);
-	    zfree(f, sizeof(*f));
-	} else
-	    f->flags &= ~MFF_ADDED;
+    int ret = 0;
 
-	return 0;
-    }
-    return -1;
+    if (f->bn_size && setbuiltins(nam, f->bn_list, f->bn_size, e) != 1)
+	ret = 1;
+    if (e)
+	e += f->bn_size;
+    if (f->cd_size && setconddefs(nam, f->cd_list, f->cd_size, e) != 1)
+	ret = 1;
+    if (e)
+	e += f->cd_size;
+    if (f->pd_size && setparamdefs(nam, f->pd_list, f->pd_size, e) != 1)
+	ret = 1;
+    if (e)
+	e += f->pd_size;
+    if (f->mf_size && setmathfuncs(nam, f->mf_list, f->mf_size, e) != 1)
+	ret = 1;
+    return ret;
 }
-
+	    
 /**/
 mod_export int
-deletemathfuncs(char const *nam, MathFunc f, int size)
+handlefeatures(char *nam, Features f, int **enables)
 {
-    int hads = 0, hadf = 0;
-
-    while (size--) {
-	if (!(f->flags & MFF_ADDED)) {
-	    f++;
-	    continue;
-	}
-	if (deletemathfunc(f)) {
-	    zwarnnam(nam, "math function `%s' already deleted", f->name);
-	    hadf = 1;
-	} else
-	    hads = 2;
-	f++;
-    }
-    return hadf ? hads : 1;
+    if (!enables || *enables)
+	return setfeatureenables(nam, f, *enables);
+    *enables = getfeatureenables(nam, f);
+    return 0;
 }
diff --git a/Src/params.c b/Src/params.c
index 14ff4f6ca..31a65811f 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -419,7 +419,7 @@ getparamnode(HashTable ht, char *nam)
     if (pm && pm->u.str && (pm->node.flags & PM_AUTOLOAD)) {
 	char *mn = dupstring(pm->u.str);
 
-	if (!load_module(mn))
+	if (load_module(mn, NULL) == 1)
 	    return NULL;
 	hn = gethashnode2(ht, nam);
 	if (((Param) hn) == pm && (pm->node.flags & PM_AUTOLOAD)) {
@@ -840,6 +840,47 @@ createparam(char *name, int flags)
     return pm;
 }
 
+/* Empty dummy function for special hash parameters. */
+
+/**/
+static void
+shempty(void)
+{
+}
+
+/* Create a simple special hash parameter. */
+
+/**/
+mod_export Param
+createspecialhash(char *name, GetNodeFunc get, ScanTabFunc scan, int flags)
+{
+    Param pm;
+    HashTable ht;
+
+    if (!(pm = createparam(name, PM_SPECIAL|PM_HASHED|flags)))
+	return NULL;
+
+    pm->level = pm->old ? locallevel : 0;
+    pm->gsu.h = (flags & PM_READONLY) ? &stdhash_gsu :
+	&nullsethash_gsu;
+    pm->u.hash = ht = newhashtable(0, name, NULL);
+
+    ht->hash        = hasher;
+    ht->emptytable  = (TableFunc) shempty;
+    ht->filltable   = NULL;
+    ht->addnode     = (AddNodeFunc) shempty;
+    ht->getnode     = ht->getnode2 = get;
+    ht->removenode  = (RemoveNodeFunc) shempty;
+    ht->disablenode = NULL;
+    ht->enablenode  = NULL;
+    ht->freenode    = (FreeNodeFunc) shempty;
+    ht->printnode   = printparamnode;
+    ht->scantab     = scan;
+
+    return pm;
+}
+
+
 /* Copy a parameter */
 
 /**/
@@ -4117,7 +4158,7 @@ scanendscope(HashNode hn, UNUSED(int flags))
 	    if (pm->env)
 		delenv(pm);
 
-	    if (!(tpm->node.flags & PM_NORESTORE))
+	    if (!(tpm->node.flags & (PM_NORESTORE|PM_READONLY)))
 		switch (PM_TYPE(pm->node.flags)) {
 		case PM_SCALAR:
 		    pm->gsu.s->setfn(pm, tpm->u.str);
diff --git a/Src/zsh.h b/Src/zsh.h
index a80a6fd99..f7255c6e7 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -77,7 +77,7 @@ typedef mnumber (*StrMathFunc)(char *, char *, int);
 struct mathfunc {
     MathFunc next;
     char *name;
-    int flags;
+    int flags;			/* MFF_* flags defined below */
     NumMathFunc nfunc;
     StrMathFunc sfunc;
     char *module;
@@ -93,6 +93,7 @@ struct mathfunc {
 /* Math function is implemented by a shell function */
 #define MFF_USERFUNC 4
 
+
 #define NUMMATHFUNC(name, func, min, max, id) \
     { NULL, name, 0, func, NULL, NULL, min, max, id }
 #define STRMATHFUNC(name, func, id) \
@@ -375,6 +376,7 @@ typedef struct builtin   *Builtin;
 typedef struct cmdnam    *Cmdnam;
 typedef struct complist  *Complist;
 typedef struct conddef   *Conddef;
+typedef struct features  *Features;
 typedef struct funcstack *Funcstack;
 typedef struct funcwrap  *FuncWrap;
 typedef struct hashnode  *HashNode;
@@ -1166,14 +1168,40 @@ struct module {
 #define MOD_INIT_B  (1<<5)
 #define MOD_ALIAS   (1<<6)
 
-typedef int (*Module_func) _((Module));
+typedef int (*Module_generic_func) _((void));
+typedef int (*Module_void_func) _((Module));
+typedef int (*Module_features_func) _((Module, char ***));
+typedef int (*Module_enables_func) _((Module, int **));
 
 struct linkedmod {
     char *name;
-    Module_func setup;
-    Module_func boot;
-    Module_func cleanup;
-    Module_func finish;
+    Module_void_func setup;
+    Module_features_func features;
+    Module_enables_func enables;
+    Module_void_func boot;
+    Module_void_func cleanup;
+    Module_void_func finish;
+};
+
+/*
+ * Structure combining all the concrete features available in
+ * a module and with space for information about abstract features.
+ */
+struct features {
+    /* List of builtins provided by the module and the size thereof */
+    Builtin bn_list;
+    int bn_size;
+    /* List of conditions provided by the module and the size thereof */
+    Conddef cd_list;
+    int cd_size;
+    /* List of parameters provided by the module and the size thereof */
+    Paramdef pd_list;
+    int pd_size;
+    /* List of math functions provided by the module and the size thereof */
+    MathFunc mf_list;
+    int mf_size;
+    /* Number of abstract features */
+    int n_abstract;
 };
 
 /* C-function hooks */
@@ -1422,26 +1450,65 @@ struct tieddata {
 #define PF_ASSIGN	0x02	/* argument handled like the RHS of foo=bar */
 #define PF_SINGLE	0x04	/* single word substitution */
 
+/*
+ * Structure for adding parameters in a module.
+ * The flags should declare the type; note PM_SCALAR is zero.
+ *
+ * Special hashes are recognized by getnfn so the PM_HASHED
+ * is optional.  These get slightly non-standard attention:
+ * the function createspecialhash is used to create them.
+ *
+ * The get/set/unset attribute may be NULL; in that case the
+ * parameter is assigned methods suitable for handling the
+ * tie variable var, if that is not NULL, else standard methods.
+ *
+ * pm is set when the parameter is added to the parameter table
+ * and serves as a flag that the parameter has been added.
+ */
 struct paramdef {
     char *name;
     int flags;
-    void *var;
-    void *gsu;			/* get/set/unset structure */
+    void *var;			/* tied internal variable, if any */
+    const void *gsu;		/* get/set/unset structure, if special */
+    GetNodeFunc getnfn;		/* function to get node, if special hash */
+    ScanTabFunc scantfn;	/* function to scan table, if special hash */
+    Param pm;			/* structure inserted into param table */
 };
 
+/*
+ * Shorthand for common uses of adding parameters, with no special
+ * hash properties.
+ */
 #define PARAMDEF(name, flags, var, gsu) \
-    { name, flags, (void *) var, (void *) gsu, }
+    { name, flags, (void *) var, (void *) gsu, \
+	    NULL, NULL, NULL \
+    }
 /*
  * Note that the following definitions are appropriate for defining
  * parameters that reference a variable (var).  Hence the get/set/unset
  * methods used will assume var needs dereferencing to get the value.
  */
 #define INTPARAMDEF(name, var) \
-    { name, PM_INTEGER, (void *) var, NULL }
+    { name, PM_INTEGER, (void *) var, NULL,  NULL, NULL, NULL }
 #define STRPARAMDEF(name, var) \
-    { name, PM_SCALAR, (void *) var, NULL }
+    { name, PM_SCALAR, (void *) var, NULL, NULL, NULL, NULL }
 #define ARRPARAMDEF(name, var) \
-    { name, PM_ARRAY, (void *) var, NULL }
+    { name, PM_ARRAY, (void *) var, NULL, NULL, NULL, NULL }
+/*
+ * The following is appropriate for a module function that behaves
+ * in a special fashion.  Parameters used in a module that don't
+ * have special behaviour shouldn't be declared in a table but
+ * should just be added with the standard parameter functions.
+ *
+ * These parameters are not marked as removable, since they
+ * shouldn't be loaded as local parameters, unlike the special
+ * Zle parameters that are added and removed on each call to Zle.
+ * We add the PM_REMOVABLE flag when removing the feature corresponding
+ * to the parameter.
+ */
+#define SPECIALPMDEF(name, flags, gsufn, getfn, scanfn) \
+    { name, flags | PM_SPECIAL | PM_HIDE | PM_HIDEVAL, \
+	    NULL, gsufn, getfn, scanfn, NULL }
 
 #define setsparam(S,V) assignsparam(S,V,0)
 #define setaparam(S,V) assignaparam(S,V,0)
diff --git a/Test/B02typeset.ztst b/Test/B02typeset.ztst
index bbc00a2ea..40669defd 100644
--- a/Test/B02typeset.ztst
+++ b/Test/B02typeset.ztst
@@ -356,8 +356,6 @@
 
  local parentenv=preserved
  fn() {
-  # The first declare works around the "not an identifier" bug with -h
-  declare \! \# \$ \* - \? @ 0
   typeset -h +g -m \*
   unset -m \*
   integer i=9
diff --git a/Test/V01zmodload.ztst b/Test/V01zmodload.ztst
index d26ae1e42..daba3e0a1 100644
--- a/Test/V01zmodload.ztst
+++ b/Test/V01zmodload.ztst
@@ -49,9 +49,11 @@
 >zmodload zsh/main
 >zmodload zsh/parameter
 
+# You use to need zmodload -i to avoid an error.
+# That has been deemed pointless, so now an attempt
+# to load a loaded module should succeed.
  zmodload zsh/main
-1:Test reloading an already-loaded module
-?(eval):zmodload:1: module zsh/main already loaded.
+0:Test reloading an already-loaded module
 
 # Loop over the modules found above and attempt to load each one.  Use
 # the -i flag in case dependencies cause multiple modules to be loaded,
diff --git a/Test/V04features.ztst b/Test/V04features.ztst
new file mode 100644
index 000000000..f5f136b65
--- /dev/null
+++ b/Test/V04features.ztst
@@ -0,0 +1,162 @@
+%prep
+
+# Do some tests on handling of features.
+# This also does some slightly more sophisticated loading and
+# unloading tests than we did in V01zmodload.ztst.
+#
+# We use zsh/datetime because it has a list of features that is short
+# but contains two types.
+
+  if ! (zmodload zsh/datetime >/dev/null 2>/dev/null); then
+    ZTST_unimplemented="can't load the zsh/datetime module for testing"
+  fi
+
+%test
+  zmodload -F zsh/datetime
+  zmodload -lF zsh/datetime
+0:Loading modules with no features
+>-b:strftime
+>-p:EPOCHSECONDS
+
+  zmodload -F zsh/datetime b:strftime
+  zmodload -lF zsh/datetime
+0:Enabling features
+>+b:strftime
+>-p:EPOCHSECONDS
+
+  zmodload -F zsh/datetime +p:EPOCHSECONDS -b:strftime
+  zmodload -lF zsh/datetime
+0:Disabling features
+>-b:strftime
+>+p:EPOCHSECONDS
+
+  zmodload -Fe zsh/datetime p:EPOCHSECONDS b:strftime
+0:Testing existing features
+
+  zmodload -Fe zsh/datetime +p:EPOCHSECONDS
+0:Testing features are in given state (on feature is on)
+
+  zmodload -Fe zsh/datetime -p:EPOCHSECONDS
+1:Testing features are in given state (on feature is not off
+
+  zmodload -Fe zsh/datetime +p:strftime
+1:Testing features are in given state (off feature is not on)
+
+  zmodload -Fe zsh/datetime -b:strftime
+0:Testing features are in given state (off feature is off
+
+  zmodload -Fe zsh/datetime p:EPOCHSECONDS b:strftime b:mktimebetter
+1:Testing non-existent features
+
+  zmodload -FlP dtf zsh/datetime
+  for feature in b:strftime p:EPOCHSECONDS; do
+    if [[ ${${dtf[(R)?$feature]}[1]} = + ]]; then
+      print $feature is enabled
+    else
+      print $feature is disabled
+    fi
+  done
+0:Testing features via array parameter
+>b:strftime is disabled
+>p:EPOCHSECONDS is enabled
+
+  fn() {
+    local EPOCHSECONDS=scruts
+    print $EPOCHSECONDS
+    print ${(t)EPOCHSECONDS}
+  }
+  fn
+  if [[ $EPOCHSECONDS = <-> ]]; then
+    print EPOCHSECONDS is a number
+  else
+    print EPOCHSECONDS is some random piece of junk
+  fi
+  print ${(t)EPOCHSECONDS}
+0:Module special parameter is hidden by a local parameter
+>scruts
+>scalar-local
+>EPOCHSECONDS is a number
+>integer-readonly-hide-hideval-special
+
+  typeset +h EPOCHSECONDS
+  fn() {
+    local EPOCHSECONDS=scruts
+    print Didn\'t get here >&2
+  }
+  fn
+1:Unhidden readonly special can't be assigned to when made local
+?fn:1: read-only variable: EPOCHSECONDS
+
+  zmodload -u zsh/datetime
+0:Module unloaded
+
+  zmodload -e zsh/datetime
+1:Module doesn't exist when unloaded
+
+  zmodload -Fe zsh/datetime p:EPOCHSECONDS
+1:Module doesn't have features when unloaded
+
+  fn() {
+    local EPOCHSECONDS=scrimf
+    zmodload zsh/datetime
+  }
+  fn
+# status is zero because load succeded although features not all enabled
+2:Failed to add parameter if local parameter present
+?fn:2: Can't add module parameter `EPOCHSECONDS': local parameter exists
+?fn:zsh/datetime:2: error when adding parameter `EPOCHSECONDS'
+
+  zmodload -lF zsh/datetime
+0:Feature state with loading after error enabling
+>+b:strftime
+>-p:EPOCHSECONDS
+
+  zmodload -F zsh/datetime p:EPOCHSECONDS
+  zmodload -Fe zsh/datetime +p:EPOCHSECONDS
+0:Successfully added feature parameter that previously failed
+
+  fn() {
+    local EPOCHSECONDS=scrooble
+    zmodload -u zsh/datetime
+    print $EPOCHSECONDS
+  }
+  fn
+  print ${+EPOCHSECONDS}
+0:Successfully unloaded a module despite a parameter being hidden
+>scrooble
+>0
+
+  EPOCHSECONDS=(any old parameter)
+  print -l $EPOCHSECONDS
+0:Using parameter as normal after unloading is OK
+>any
+>old
+>parameter
+
+  print strftime is ${builtins[strftime]:-undefined}
+  zmodload -F zsh/datetime b:strftime
+  print strftime is ${builtins[strftime]:-undefined}
+  zmodload -F zsh/datetime -b:strftime
+  print strftime is ${builtins[strftime]:-undefined}
+0:Enabling and disabling of builtins as features
+>strftime is undefined
+>strftime is defined
+>strftime is undefined
+
+  zmodload -u zsh/datetime
+  zmodload zsh/datetime
+2:Loading won't override global parameter
+?(eval):2: Can't add module parameter `EPOCHSECONDS': parameter already exists
+?(eval):zsh/datetime:2: error when adding parameter `EPOCHSECONDS'
+
+  unset EPOCHSECONDS
+  zmodload -F zsh/datetime p:EPOCHSECONDS
+  zmodload -Fe zsh/datetime +p:EPOCHSECONDS
+0:unsetting a global parameter allows feature parameter to be enabled
+
+  zmodload -F zsh/datetime -b:strftime -p:EPOCHSECONDS
+  zmodload zsh/datetime
+  zmodload -lF zsh/datetime
+0:zmodload with no -F enables all features
+>+b:strftime
+>+p:EPOCHSECONDS