summaryrefslogtreecommitdiff
path: root/dotfiles/system/.zsh/modules/Src/params.c
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2025-05-08 18:49:34 -0500
committerCraig Jennings <c@cjennings.net>2025-05-08 18:51:59 -0500
commit000e00871830cd15de032c80e2b62946cf19445c (patch)
tree794a7922750472bbe0e024042d6ba84f411fc3e0 /dotfiles/system/.zsh/modules/Src/params.c
parentfe302606931e4bad91c4ed6df81a4403523ba780 (diff)
adding missing dotfiles and folders
- profile.d/ - bashrc - authinfo.gpg - .zsh/
Diffstat (limited to 'dotfiles/system/.zsh/modules/Src/params.c')
-rw-r--r--dotfiles/system/.zsh/modules/Src/params.c5884
1 files changed, 5884 insertions, 0 deletions
diff --git a/dotfiles/system/.zsh/modules/Src/params.c b/dotfiles/system/.zsh/modules/Src/params.c
new file mode 100644
index 0000000..a1c299f
--- /dev/null
+++ b/dotfiles/system/.zsh/modules/Src/params.c
@@ -0,0 +1,5884 @@
+/*
+ * params.c - parameters
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 1992-1997 Paul Falstad
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Paul Falstad or the Zsh Development Group be liable
+ * to any party for direct, indirect, special, incidental, or consequential
+ * damages arising out of the use of this software and its documentation,
+ * even if Paul Falstad and the Zsh Development Group have been advised of
+ * the possibility of such damage.
+ *
+ * Paul Falstad and the Zsh Development Group specifically disclaim any
+ * warranties, including, but not limited to, the implied warranties of
+ * merchantability and fitness for a particular purpose. The software
+ * provided hereunder is on an "as is" basis, and Paul Falstad and the
+ * Zsh Development Group have no obligation to provide maintenance,
+ * support, updates, enhancements, or modifications.
+ *
+ */
+
+#include "zsh.mdh"
+#include "params.pro"
+
+#include "version.h"
+#ifdef CUSTOM_PATCHLEVEL
+#define ZSH_PATCHLEVEL CUSTOM_PATCHLEVEL
+#else
+#include "patchlevel.h"
+
+#include <math.h>
+
+/* If removed from the ChangeLog for some reason */
+#ifndef ZSH_PATCHLEVEL
+#define ZSH_PATCHLEVEL "unknown"
+#endif
+#endif
+
+/* what level of localness we are at */
+
+/**/
+mod_export int locallevel;
+
+/* Variables holding values of special parameters */
+
+/**/
+mod_export
+char **pparams, /* $argv */
+ **cdpath, /* $cdpath */
+ **fpath, /* $fpath */
+ **mailpath, /* $mailpath */
+ **manpath, /* $manpath */
+ **psvar, /* $psvar */
+ **watch, /* $watch */
+ **zsh_eval_context; /* $zsh_eval_context */
+/**/
+mod_export
+char **path, /* $path */
+ **fignore; /* $fignore */
+
+/**/
+mod_export
+char *argzero, /* $0 */
+ *posixzero, /* $0 */
+ *home, /* $HOME */
+ *nullcmd, /* $NULLCMD */
+ *oldpwd, /* $OLDPWD */
+ *zoptarg, /* $OPTARG */
+ *prompt, /* $PROMPT */
+ *prompt2, /* $PROMPT2 */
+ *prompt3, /* $PROMPT3 */
+ *prompt4, /* $PROMPT4 */
+ *readnullcmd, /* $READNULLCMD */
+ *rprompt, /* $RPROMPT */
+ *rprompt2, /* $RPROMPT2 */
+ *sprompt, /* $SPROMPT */
+ *wordchars; /* $WORDCHARS */
+/**/
+mod_export
+char *ifs, /* $IFS */
+ *postedit, /* $POSTEDIT */
+ *term, /* $TERM */
+ *zsh_terminfo, /* $TERMINFO */
+ *zsh_terminfodirs, /* $TERMINFO_DIRS */
+ *ttystrname, /* $TTY */
+ *pwd; /* $PWD */
+
+/**/
+mod_export
+zlong lastval, /* $? */
+ mypid, /* $$ */
+ lastpid, /* $! */
+ zterm_columns, /* $COLUMNS */
+ zterm_lines, /* $LINES */
+ rprompt_indent, /* $ZLE_RPROMPT_INDENT */
+ ppid, /* $PPID */
+ zsh_subshell; /* $ZSH_SUBSHELL */
+
+/* $FUNCNEST */
+/**/
+mod_export
+zlong zsh_funcnest =
+#ifdef MAX_FUNCTION_DEPTH
+ MAX_FUNCTION_DEPTH
+#else
+ /* Disabled by default but can be enabled at run time */
+ -1
+#endif
+ ;
+
+/**/
+zlong lineno, /* $LINENO */
+ zoptind, /* $OPTIND */
+ shlvl; /* $SHLVL */
+
+/* $histchars */
+
+/**/
+mod_export unsigned char bangchar;
+/**/
+unsigned char hatchar, hashchar;
+
+/**/
+unsigned char keyboardhackchar = '\0';
+
+/* $SECONDS = now.tv_sec - shtimer.tv_sec
+ * + (now.tv_usec - shtimer.tv_usec) / 1000000.0
+ * (rounded to an integer if the parameter is not set to float) */
+
+/**/
+struct timeval shtimer;
+
+/* 0 if this $TERM setup is usable, otherwise it contains TERM_* flags */
+
+/**/
+mod_export int termflags;
+
+/* Forward declaration */
+
+static void
+rprompt_indent_unsetfn(Param pm, int exp);
+
+/* Standard methods for get/set/unset pointers in parameters */
+
+/**/
+mod_export const struct gsu_scalar stdscalar_gsu =
+{ strgetfn, strsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_scalar varscalar_gsu =
+{ strvargetfn, strvarsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_scalar nullsetscalar_gsu =
+{ strgetfn, nullstrsetfn, NULL };
+
+/**/
+mod_export const struct gsu_integer stdinteger_gsu =
+{ intgetfn, intsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_integer varinteger_gsu =
+{ intvargetfn, intvarsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_integer nullsetinteger_gsu =
+{ intgetfn, NULL, NULL };
+
+/**/
+mod_export const struct gsu_float stdfloat_gsu =
+{ floatgetfn, floatsetfn, stdunsetfn };
+
+/**/
+mod_export const struct gsu_array stdarray_gsu =
+{ arrgetfn, arrsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_array vararray_gsu =
+{ arrvargetfn, arrvarsetfn, stdunsetfn };
+
+/**/
+mod_export const struct gsu_hash stdhash_gsu =
+{ hashgetfn, hashsetfn, stdunsetfn };
+/**/
+mod_export const struct gsu_hash nullsethash_gsu =
+{ hashgetfn, nullsethashfn, nullunsetfn };
+
+
+/* Non standard methods (not exported) */
+static const struct gsu_integer pound_gsu =
+{ poundgetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_integer errno_gsu =
+{ errnogetfn, errnosetfn, stdunsetfn };
+static const struct gsu_integer gid_gsu =
+{ gidgetfn, gidsetfn, stdunsetfn };
+static const struct gsu_integer egid_gsu =
+{ egidgetfn, egidsetfn, stdunsetfn };
+static const struct gsu_integer histsize_gsu =
+{ histsizegetfn, histsizesetfn, stdunsetfn };
+static const struct gsu_integer random_gsu =
+{ randomgetfn, randomsetfn, stdunsetfn };
+static const struct gsu_integer savehist_gsu =
+{ savehistsizegetfn, savehistsizesetfn, stdunsetfn };
+static const struct gsu_integer intseconds_gsu =
+{ intsecondsgetfn, intsecondssetfn, stdunsetfn };
+static const struct gsu_float floatseconds_gsu =
+{ floatsecondsgetfn, floatsecondssetfn, stdunsetfn };
+static const struct gsu_integer uid_gsu =
+{ uidgetfn, uidsetfn, stdunsetfn };
+static const struct gsu_integer euid_gsu =
+{ euidgetfn, euidsetfn, stdunsetfn };
+static const struct gsu_integer ttyidle_gsu =
+{ ttyidlegetfn, nullintsetfn, stdunsetfn };
+
+static const struct gsu_scalar argzero_gsu =
+{ argzerogetfn, argzerosetfn, nullunsetfn };
+static const struct gsu_scalar username_gsu =
+{ usernamegetfn, usernamesetfn, stdunsetfn };
+static const struct gsu_scalar dash_gsu =
+{ dashgetfn, nullstrsetfn, stdunsetfn };
+static const struct gsu_scalar histchars_gsu =
+{ histcharsgetfn, histcharssetfn, stdunsetfn };
+static const struct gsu_scalar home_gsu =
+{ homegetfn, homesetfn, stdunsetfn };
+static const struct gsu_scalar term_gsu =
+{ termgetfn, termsetfn, stdunsetfn };
+static const struct gsu_scalar terminfo_gsu =
+{ terminfogetfn, terminfosetfn, stdunsetfn };
+static const struct gsu_scalar terminfodirs_gsu =
+{ terminfodirsgetfn, terminfodirssetfn, stdunsetfn };
+static const struct gsu_scalar wordchars_gsu =
+{ wordcharsgetfn, wordcharssetfn, stdunsetfn };
+static const struct gsu_scalar ifs_gsu =
+{ ifsgetfn, ifssetfn, stdunsetfn };
+static const struct gsu_scalar underscore_gsu =
+{ underscoregetfn, nullstrsetfn, stdunsetfn };
+static const struct gsu_scalar keyboard_hack_gsu =
+{ keyboardhackgetfn, keyboardhacksetfn, stdunsetfn };
+#ifdef USE_LOCALE
+static const struct gsu_scalar lc_blah_gsu =
+{ strgetfn, lcsetfn, stdunsetfn };
+static const struct gsu_scalar lang_gsu =
+{ strgetfn, langsetfn, stdunsetfn };
+static const struct gsu_scalar lc_all_gsu =
+{ strgetfn, lc_allsetfn, stdunsetfn };
+#endif
+
+static const struct gsu_integer varint_readonly_gsu =
+{ intvargetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_integer zlevar_gsu =
+{ intvargetfn, zlevarsetfn, stdunsetfn };
+
+static const struct gsu_scalar colonarr_gsu =
+{ colonarrgetfn, colonarrsetfn, stdunsetfn };
+
+static const struct gsu_integer argc_gsu =
+{ poundgetfn, nullintsetfn, stdunsetfn };
+static const struct gsu_array pipestatus_gsu =
+{ pipestatgetfn, pipestatsetfn, stdunsetfn };
+
+static const struct gsu_integer rprompt_indent_gsu =
+{ intvargetfn, zlevarsetfn, rprompt_indent_unsetfn };
+
+/* Nodes for special parameters for parameter hash table */
+
+#ifdef HAVE_UNION_INIT
+# define BR(X) {X}
+typedef struct param initparam;
+#else
+# define BR(X) X
+typedef struct iparam {
+ struct hashnode *next;
+ char *nam; /* hash data */
+ int flags; /* PM_* flags (defined in zsh.h) */
+ void *value;
+ void *gsu; /* get/set/unset methods */
+ int base; /* output base */
+ int width; /* output field width */
+ char *env; /* location in environment, if exported */
+ char *ename; /* name of corresponding environment var */
+ Param old; /* old struct for use with local */
+ int level; /* if (old != NULL), level of localness */
+} initparam;
+#endif
+
+static initparam special_params[] ={
+#define GSU(X) BR((GsuScalar)(void *)(&(X)))
+#define NULL_GSU BR((GsuScalar)(void *)NULL)
+#define IPDEF1(A,B,C) {{NULL,A,PM_INTEGER|PM_SPECIAL|C},BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0}
+IPDEF1("#", pound_gsu, PM_READONLY),
+IPDEF1("ERRNO", errno_gsu, PM_UNSET),
+IPDEF1("GID", gid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EGID", egid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("HISTSIZE", histsize_gsu, PM_RESTRICTED),
+IPDEF1("RANDOM", random_gsu, 0),
+IPDEF1("SAVEHIST", savehist_gsu, PM_RESTRICTED),
+IPDEF1("SECONDS", intseconds_gsu, 0),
+IPDEF1("UID", uid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("EUID", euid_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF1("TTYIDLE", ttyidle_gsu, PM_READONLY),
+
+#define IPDEF2(A,B,C) {{NULL,A,PM_SCALAR|PM_SPECIAL|C},BR(NULL),GSU(B),0,0,NULL,NULL,NULL,0}
+IPDEF2("USERNAME", username_gsu, PM_DONTIMPORT|PM_RESTRICTED),
+IPDEF2("-", dash_gsu, PM_READONLY),
+IPDEF2("histchars", histchars_gsu, PM_DONTIMPORT),
+IPDEF2("HOME", home_gsu, PM_UNSET),
+IPDEF2("TERM", term_gsu, PM_UNSET),
+IPDEF2("TERMINFO", terminfo_gsu, PM_UNSET),
+IPDEF2("TERMINFO_DIRS", terminfodirs_gsu, PM_UNSET),
+IPDEF2("WORDCHARS", wordchars_gsu, 0),
+IPDEF2("IFS", ifs_gsu, PM_DONTIMPORT | PM_RESTRICTED),
+IPDEF2("_", underscore_gsu, PM_DONTIMPORT),
+IPDEF2("KEYBOARD_HACK", keyboard_hack_gsu, PM_DONTIMPORT),
+IPDEF2("0", argzero_gsu, 0),
+
+#ifdef USE_LOCALE
+# define LCIPDEF(name) IPDEF2(name, lc_blah_gsu, PM_UNSET)
+IPDEF2("LANG", lang_gsu, PM_UNSET),
+IPDEF2("LC_ALL", lc_all_gsu, PM_UNSET),
+# ifdef LC_COLLATE
+LCIPDEF("LC_COLLATE"),
+# endif
+# ifdef LC_CTYPE
+LCIPDEF("LC_CTYPE"),
+# endif
+# ifdef LC_MESSAGES
+LCIPDEF("LC_MESSAGES"),
+# endif
+# ifdef LC_NUMERIC
+LCIPDEF("LC_NUMERIC"),
+# endif
+# ifdef LC_TIME
+LCIPDEF("LC_TIME"),
+# endif
+#endif /* USE_LOCALE */
+
+#define IPDEF4(A,B) {{NULL,A,PM_INTEGER|PM_READONLY|PM_SPECIAL},BR((void *)B),GSU(varint_readonly_gsu),10,0,NULL,NULL,NULL,0}
+IPDEF4("!", &lastpid),
+IPDEF4("$", &mypid),
+IPDEF4("?", &lastval),
+IPDEF4("HISTCMD", &curhist),
+IPDEF4("LINENO", &lineno),
+IPDEF4("PPID", &ppid),
+IPDEF4("ZSH_SUBSHELL", &zsh_subshell),
+
+#define IPDEF5(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0}
+#define IPDEF5U(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0}
+IPDEF5("COLUMNS", &zterm_columns, zlevar_gsu),
+IPDEF5("LINES", &zterm_lines, zlevar_gsu),
+IPDEF5U("ZLE_RPROMPT_INDENT", &rprompt_indent, rprompt_indent_gsu),
+IPDEF5("SHLVL", &shlvl, varinteger_gsu),
+IPDEF5("FUNCNEST", &zsh_funcnest, varinteger_gsu),
+
+/* Don't import internal integer status variables. */
+#define IPDEF6(A,B,F) {{NULL,A,PM_INTEGER|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(F),10,0,NULL,NULL,NULL,0}
+IPDEF6("OPTIND", &zoptind, varinteger_gsu),
+IPDEF6("TRY_BLOCK_ERROR", &try_errflag, varinteger_gsu),
+IPDEF6("TRY_BLOCK_INTERRUPT", &try_interrupt, varinteger_gsu),
+
+#define IPDEF7(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+#define IPDEF7R(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_DONTIMPORT_SUID},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+#define IPDEF7U(A,B) {{NULL,A,PM_SCALAR|PM_SPECIAL|PM_UNSET},BR((void *)B),GSU(varscalar_gsu),0,0,NULL,NULL,NULL,0}
+IPDEF7("OPTARG", &zoptarg),
+IPDEF7("NULLCMD", &nullcmd),
+IPDEF7U("POSTEDIT", &postedit),
+IPDEF7("READNULLCMD", &readnullcmd),
+IPDEF7("PS1", &prompt),
+IPDEF7U("RPS1", &rprompt),
+IPDEF7U("RPROMPT", &rprompt),
+IPDEF7("PS2", &prompt2),
+IPDEF7U("RPS2", &rprompt2),
+IPDEF7U("RPROMPT2", &rprompt2),
+IPDEF7("PS3", &prompt3),
+IPDEF7R("PS4", &prompt4),
+IPDEF7("SPROMPT", &sprompt),
+
+#define IPDEF9F(A,B,C,D) {{NULL,A,D|PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT},BR((void *)B),GSU(vararray_gsu),0,0,NULL,C,NULL,0}
+#define IPDEF9(A,B,C) IPDEF9F(A,B,C,0)
+IPDEF9F("*", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
+IPDEF9F("@", &pparams, NULL, PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT|PM_READONLY),
+
+/*
+ * This empty row indicates the end of parameters available in
+ * all emulations.
+ */
+{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+
+#define IPDEF8(A,B,C,D) {{NULL,A,D|PM_SCALAR|PM_SPECIAL},BR((void *)B),GSU(colonarr_gsu),0,0,NULL,C,NULL,0}
+IPDEF8("CDPATH", &cdpath, "cdpath", 0),
+IPDEF8("FIGNORE", &fignore, "fignore", 0),
+IPDEF8("FPATH", &fpath, "fpath", 0),
+IPDEF8("MAILPATH", &mailpath, "mailpath", 0),
+IPDEF8("WATCH", &watch, "watch", 0),
+IPDEF8("PATH", &path, "path", PM_RESTRICTED),
+IPDEF8("PSVAR", &psvar, "psvar", 0),
+IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, "zsh_eval_context", PM_READONLY),
+
+/* MODULE_PATH is not imported for security reasons */
+IPDEF8("MODULE_PATH", &module_path, "module_path", PM_DONTIMPORT|PM_RESTRICTED),
+
+#define IPDEF10(A,B) {{NULL,A,PM_ARRAY|PM_SPECIAL},BR(NULL),GSU(B),10,0,NULL,NULL,NULL,0}
+
+/*
+ * The following parameters are not available in sh/ksh compatibility *
+ * mode.
+ */
+
+/* All of these have sh compatible equivalents. */
+IPDEF1("ARGC", argc_gsu, PM_READONLY),
+IPDEF2("HISTCHARS", histchars_gsu, PM_DONTIMPORT),
+IPDEF4("status", &lastval),
+IPDEF7("prompt", &prompt),
+IPDEF7("PROMPT", &prompt),
+IPDEF7("PROMPT2", &prompt2),
+IPDEF7("PROMPT3", &prompt3),
+IPDEF7("PROMPT4", &prompt4),
+IPDEF8("MANPATH", &manpath, "manpath", 0),
+IPDEF9("argv", &pparams, NULL),
+IPDEF9("fignore", &fignore, "FIGNORE"),
+IPDEF9("cdpath", &cdpath, "CDPATH"),
+IPDEF9("fpath", &fpath, "FPATH"),
+IPDEF9("mailpath", &mailpath, "MAILPATH"),
+IPDEF9("manpath", &manpath, "MANPATH"),
+IPDEF9("psvar", &psvar, "PSVAR"),
+IPDEF9("watch", &watch, "WATCH"),
+
+IPDEF9F("zsh_eval_context", &zsh_eval_context, "ZSH_EVAL_CONTEXT", PM_READONLY),
+
+IPDEF9F("module_path", &module_path, "MODULE_PATH", PM_RESTRICTED),
+IPDEF9F("path", &path, "PATH", PM_RESTRICTED),
+
+/* These are known to zsh alone. */
+
+IPDEF10("pipestatus", pipestatus_gsu),
+
+{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+};
+
+/*
+ * Alternative versions of colon-separated path parameters for
+ * sh emulation. These don't link to the array versions.
+ */
+static initparam special_params_sh[] = {
+IPDEF8("CDPATH", &cdpath, NULL, 0),
+IPDEF8("FIGNORE", &fignore, NULL, 0),
+IPDEF8("FPATH", &fpath, NULL, 0),
+IPDEF8("MAILPATH", &mailpath, NULL, 0),
+IPDEF8("WATCH", &watch, NULL, 0),
+IPDEF8("PATH", &path, NULL, PM_RESTRICTED),
+IPDEF8("PSVAR", &psvar, NULL, 0),
+IPDEF8("ZSH_EVAL_CONTEXT", &zsh_eval_context, NULL, PM_READONLY),
+
+/* MODULE_PATH is not imported for security reasons */
+IPDEF8("MODULE_PATH", &module_path, NULL, PM_DONTIMPORT|PM_RESTRICTED),
+
+{{NULL,NULL,0},BR(NULL),NULL_GSU,0,0,NULL,NULL,NULL,0},
+};
+
+/*
+ * Special way of referring to the positional parameters. Unlike $*
+ * and $@, this is not readonly. This parameter is not directly
+ * visible in user space.
+ */
+static initparam argvparam_pm = IPDEF9F("", &pparams, NULL, \
+ PM_ARRAY|PM_SPECIAL|PM_DONTIMPORT);
+
+#undef BR
+
+#define IS_UNSET_VALUE(V) \
+ ((V) && (!(V)->pm || ((V)->pm->node.flags & PM_UNSET) || \
+ !(V)->pm->node.nam || !*(V)->pm->node.nam))
+
+static Param argvparam;
+
+/* hash table containing the parameters */
+
+/**/
+mod_export HashTable paramtab, realparamtab;
+
+/**/
+mod_export HashTable
+newparamtable(int size, char const *name)
+{
+ HashTable ht;
+ if (!size)
+ size = 17;
+ ht = newhashtable(size, name, NULL);
+
+ ht->hash = hasher;
+ ht->emptytable = emptyhashtable;
+ ht->filltable = NULL;
+ ht->cmpnodes = strcmp;
+ ht->addnode = addhashnode;
+ ht->getnode = getparamnode;
+ ht->getnode2 = gethashnode2;
+ ht->removenode = removehashnode;
+ ht->disablenode = NULL;
+ ht->enablenode = NULL;
+ ht->freenode = freeparamnode;
+ ht->printnode = printparamnode;
+
+ return ht;
+}
+
+/**/
+static HashNode
+getparamnode(HashTable ht, const char *nam)
+{
+ HashNode hn = gethashnode2(ht, nam);
+ Param pm = (Param) hn;
+
+ if (pm && pm->u.str && (pm->node.flags & PM_AUTOLOAD)) {
+ char *mn = dupstring(pm->u.str);
+
+ (void)ensurefeature(mn, "p:", (pm->node.flags & PM_AUTOALL) ? NULL :
+ nam);
+ hn = gethashnode2(ht, nam);
+ if (!hn) {
+ /*
+ * This used to be a warning, but surely if we allow
+ * stuff to go ahead with the autoload stub with
+ * no error status we're in for all sorts of mayhem?
+ */
+ zerr("autoloading module %s failed to define parameter: %s", mn,
+ nam);
+ }
+ }
+ return hn;
+}
+
+/* Copy a parameter hash table */
+
+static HashTable outtable;
+
+/**/
+static void
+scancopyparams(HashNode hn, UNUSED(int flags))
+{
+ /* Going into a real parameter, so always use permanent storage */
+ Param pm = (Param)hn;
+ Param tpm = (Param) zshcalloc(sizeof *tpm);
+ tpm->node.nam = ztrdup(pm->node.nam);
+ copyparam(tpm, pm, 0);
+ addhashnode(outtable, tpm->node.nam, tpm);
+}
+
+/**/
+HashTable
+copyparamtable(HashTable ht, char *name)
+{
+ HashTable nht = 0;
+ if (ht) {
+ nht = newparamtable(ht->hsize, name);
+ outtable = nht;
+ scanhashtable(ht, 0, 0, 0, scancopyparams, 0);
+ outtable = NULL;
+ }
+ return nht;
+}
+
+/* Flag to freeparamnode to unset the struct */
+
+static int delunset;
+
+/* Function to delete a parameter table. */
+
+/**/
+mod_export void
+deleteparamtable(HashTable t)
+{
+ /* The parameters in the hash table need to be unset *
+ * before being deleted. */
+ int odelunset = delunset;
+ delunset = 1;
+ deletehashtable(t);
+ delunset = odelunset;
+}
+
+static unsigned numparamvals;
+
+/**/
+mod_export void
+scancountparams(UNUSED(HashNode hn), int flags)
+{
+ ++numparamvals;
+ if ((flags & SCANPM_WANTKEYS) && (flags & SCANPM_WANTVALS))
+ ++numparamvals;
+}
+
+static Patprog scanprog;
+static char *scanstr;
+static char **paramvals;
+static Param foundparam;
+
+/**/
+static void
+scanparamvals(HashNode hn, int flags)
+{
+ struct value v;
+ Patprog prog;
+
+ if (numparamvals && !(flags & SCANPM_MATCHMANY) &&
+ (flags & (SCANPM_MATCHVAL|SCANPM_MATCHKEY|SCANPM_KEYMATCH)))
+ return;
+ v.pm = (Param)hn;
+ if ((flags & SCANPM_KEYMATCH)) {
+ char *tmp = dupstring(v.pm->node.nam);
+
+ tokenize(tmp);
+ remnulargs(tmp);
+
+ if (!(prog = patcompile(tmp, 0, NULL)) || !pattry(prog, scanstr))
+ return;
+ } else if ((flags & SCANPM_MATCHKEY) && !pattry(scanprog, v.pm->node.nam)) {
+ return;
+ }
+ foundparam = v.pm;
+ if (flags & SCANPM_WANTKEYS) {
+ paramvals[numparamvals++] = v.pm->node.nam;
+ if (!(flags & (SCANPM_WANTVALS|SCANPM_MATCHVAL)))
+ return;
+ }
+ v.isarr = (PM_TYPE(v.pm->node.flags) & (PM_ARRAY|PM_HASHED));
+ v.flags = 0;
+ v.start = 0;
+ v.end = -1;
+ paramvals[numparamvals] = getstrvalue(&v);
+ if (flags & SCANPM_MATCHVAL) {
+ if (pattry(scanprog, paramvals[numparamvals])) {
+ numparamvals += ((flags & SCANPM_WANTVALS) ? 1 :
+ !(flags & SCANPM_WANTKEYS));
+ } else if (flags & SCANPM_WANTKEYS)
+ --numparamvals; /* Value didn't match, discard key */
+ } else
+ ++numparamvals;
+ foundparam = NULL;
+}
+
+/**/
+char **
+paramvalarr(HashTable ht, int flags)
+{
+ DPUTS((flags & (SCANPM_MATCHKEY|SCANPM_MATCHVAL)) && !scanprog,
+ "BUG: scanning hash without scanprog set");
+ numparamvals = 0;
+ if (ht)
+ scanhashtable(ht, 0, 0, PM_UNSET, scancountparams, flags);
+ paramvals = (char **) zhalloc((numparamvals + 1) * sizeof(char *));
+ if (ht) {
+ numparamvals = 0;
+ scanhashtable(ht, 0, 0, PM_UNSET, scanparamvals, flags);
+ }
+ paramvals[numparamvals] = 0;
+ return paramvals;
+}
+
+/* Return the full array (no indexing) referred to by a Value. *
+ * The array value is cached for the lifetime of the Value. */
+
+/**/
+static char **
+getvaluearr(Value v)
+{
+ if (v->arr)
+ return v->arr;
+ else if (PM_TYPE(v->pm->node.flags) == PM_ARRAY)
+ return v->arr = v->pm->gsu.a->getfn(v->pm);
+ else if (PM_TYPE(v->pm->node.flags) == PM_HASHED) {
+ v->arr = paramvalarr(v->pm->gsu.h->getfn(v->pm), v->isarr);
+ /* Can't take numeric slices of associative arrays */
+ v->start = 0;
+ v->end = numparamvals + 1;
+ return v->arr;
+ } else
+ return NULL;
+}
+
+/* Return whether the variable is set *
+ * checks that array slices are within range *
+ * used for [[ -v ... ]] condition test */
+
+/**/
+int
+issetvar(char *name)
+{
+ struct value vbuf;
+ Value v;
+ int slice;
+ char **arr;
+
+ if (!(v = getvalue(&vbuf, &name, 1)) || *name)
+ return 0; /* no value or more chars after the variable name */
+ if (v->isarr & ~SCANPM_ARRONLY)
+ return v->end > 1; /* for extracted elements, end gives us a count */
+
+ slice = v->start != 0 || v->end != -1;
+ if (PM_TYPE(v->pm->node.flags) != PM_ARRAY || !slice)
+ return !slice && !(v->pm->node.flags & PM_UNSET);
+
+ if (!v->end) /* empty array slice */
+ return 0;
+ /* get the array and check end is within range */
+ if (!(arr = getvaluearr(v)))
+ return 0;
+ return arrlen_ge(arr, v->end < 0 ? - v->end : v->end);
+}
+
+/*
+ * Split environment string into (name, value) pair.
+ * this is used to avoid in-place editing of environment table
+ * that results in core dump on some systems
+ */
+
+static int
+split_env_string(char *env, char **name, char **value)
+{
+ char *str, *tenv;
+
+ if (!env || !name || !value)
+ return 0;
+
+ tenv = strcpy(zhalloc(strlen(env) + 1), env);
+ for (str = tenv; *str && *str != '='; str++) {
+ if (STOUC(*str) >= 128) {
+ /*
+ * We'll ignore environment variables with names not
+ * from the portable character set since we don't
+ * know of a good reason to accept them.
+ */
+ return 0;
+ }
+ }
+ if (str != tenv && *str == '=') {
+ *str = '\0';
+ *name = tenv;
+ *value = str + 1;
+ return 1;
+ } else
+ return 0;
+}
+
+/**
+ * Check parameter flags to see if parameter shouldn't be imported
+ * from environment at start.
+ *
+ * return 1: don't import: 0: ok to import.
+ */
+static int dontimport(int flags)
+{
+ /* If explicitly marked as don't export */
+ if (flags & PM_DONTIMPORT)
+ return 1;
+ /* If value already exported */
+ if (flags & PM_EXPORTED)
+ return 1;
+ /* If security issue when importing and running with some privilege */
+ if ((flags & PM_DONTIMPORT_SUID) && isset(PRIVILEGED))
+ return 1;
+ /* OK to import */
+ return 0;
+}
+
+/* Set up parameter hash table. This will add predefined *
+ * parameter entries as well as setting up parameter table *
+ * entries for environment variables we inherit. */
+
+/**/
+void
+createparamtable(void)
+{
+ Param ip, pm;
+#if !defined(HAVE_PUTENV) && !defined(USE_SET_UNSET_ENV)
+ char **new_environ;
+ int envsize;
+#endif
+#ifndef USE_SET_UNSET_ENV
+ char **envp;
+#endif
+ char **envp2, **sigptr, **t;
+ char buf[50], *str, *iname, *ivalue, *hostnam;
+ int oae = opts[ALLEXPORT];
+#ifdef HAVE_UNAME
+ struct utsname unamebuf;
+ char *machinebuf;
+#endif
+
+ paramtab = realparamtab = newparamtable(151, "paramtab");
+
+ /* Add the special parameters to the hash table */
+ for (ip = special_params; ip->node.nam; ip++)
+ paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
+ if (EMULATION(EMULATE_SH|EMULATE_KSH)) {
+ for (ip = special_params_sh; ip->node.nam; ip++)
+ paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
+ } else {
+ while ((++ip)->node.nam)
+ paramtab->addnode(paramtab, ztrdup(ip->node.nam), ip);
+ }
+
+ argvparam = (Param) &argvparam_pm;
+
+ noerrs = 2;
+
+ /* Add the standard non-special parameters which have to *
+ * be initialized before we copy the environment variables. *
+ * We don't want to override whatever values the user has *
+ * given them in the environment. */
+ opts[ALLEXPORT] = 0;
+ setiparam("MAILCHECK", 60);
+ setiparam("LOGCHECK", 60);
+ setiparam("KEYTIMEOUT", 40);
+ setiparam("LISTMAX", 100);
+ /*
+ * We used to get the output baud rate here. However, that's
+ * pretty irrelevant to a terminal on an X display and can lead
+ * to unnecessary delays if it's wrong (which it probably is).
+ * Furthermore, even if the output is slow it's very likely
+ * to be because of WAN delays, not covered by the output
+ * baud rate.
+ * So allow the user to set it in the special cases where it's
+ * useful.
+ */
+ setsparam("TMPPREFIX", ztrdup_metafy(DEFAULT_TMPPREFIX));
+ setsparam("TIMEFMT", ztrdup_metafy(DEFAULT_TIMEFMT));
+ setsparam("WATCHFMT", ztrdup_metafy(default_watchfmt));
+
+ hostnam = (char *)zalloc(256);
+ gethostname(hostnam, 256);
+ setsparam("HOST", ztrdup_metafy(hostnam));
+ zfree(hostnam, 256);
+
+ setsparam("LOGNAME",
+ ztrdup_metafy((str = getlogin()) && *str ?
+ str : cached_username));
+
+#if !defined(HAVE_PUTENV) && !defined(USE_SET_UNSET_ENV)
+ /* Copy the environment variables we are inheriting to dynamic *
+ * memory, so we can do mallocs and frees on it. */
+ envsize = sizeof(char *)*(1 + arrlen(environ));
+ new_environ = (char **) zalloc(envsize);
+ memcpy(new_environ, environ, envsize);
+ environ = new_environ;
+#endif
+
+ /* Use heap allocation to avoid many small alloc/free calls */
+ pushheap();
+
+ /* Now incorporate environment variables we are inheriting *
+ * into the parameter hash table. Copy them into dynamic *
+ * memory so that we can free them if needed */
+ for (
+#ifndef USE_SET_UNSET_ENV
+ envp =
+#endif
+ envp2 = environ; *envp2; envp2++) {
+ if (split_env_string(*envp2, &iname, &ivalue)) {
+ if (!idigit(*iname) && isident(iname) && !strchr(iname, '[')) {
+ /*
+ * Parameters that aren't already in the parameter table
+ * aren't special to the shell, so it's always OK to
+ * import. Otherwise, check parameter flags.
+ */
+ if ((!(pm = (Param) paramtab->getnode(paramtab, iname)) ||
+ !dontimport(pm->node.flags)) &&
+ (pm = assignsparam(iname, metafy(ivalue, -1, META_DUP),
+ ASSPM_ENV_IMPORT))) {
+ pm->node.flags |= PM_EXPORTED;
+ if (pm->node.flags & PM_SPECIAL)
+ pm->env = mkenvstr (pm->node.nam,
+ getsparam(pm->node.nam), pm->node.flags);
+ else
+ pm->env = ztrdup(*envp2);
+#ifndef USE_SET_UNSET_ENV
+ *envp++ = pm->env;
+#endif
+ }
+ }
+ }
+ }
+ popheap();
+#ifndef USE_SET_UNSET_ENV
+ *envp = NULL;
+#endif
+ opts[ALLEXPORT] = oae;
+
+ /*
+ * For native emulation we always set the variable home
+ * (see setupvals()).
+ */
+ pm = (Param) paramtab->getnode(paramtab, "HOME");
+ if (EMULATION(EMULATE_ZSH))
+ {
+ pm->node.flags &= ~PM_UNSET;
+ if (!(pm->node.flags & PM_EXPORTED))
+ addenv(pm, home);
+ } else if (!home)
+ pm->node.flags |= PM_UNSET;
+ pm = (Param) paramtab->getnode(paramtab, "LOGNAME");
+ if (!(pm->node.flags & PM_EXPORTED))
+ addenv(pm, pm->u.str);
+ pm = (Param) paramtab->getnode(paramtab, "SHLVL");
+ sprintf(buf, "%d", (int)++shlvl);
+ /* shlvl value in environment needs updating unconditionally */
+ addenv(pm, buf);
+
+ /* Add the standard non-special parameters */
+ set_pwd_env();
+#ifdef HAVE_UNAME
+ if(uname(&unamebuf)) setsparam("CPUTYPE", ztrdup("unknown"));
+ else
+ {
+ machinebuf = ztrdup_metafy(unamebuf.machine);
+ setsparam("CPUTYPE", machinebuf);
+ }
+
+#else
+ setsparam("CPUTYPE", ztrdup_metafy("unknown"));
+#endif
+ setsparam("MACHTYPE", ztrdup_metafy(MACHTYPE));
+ setsparam("OSTYPE", ztrdup_metafy(OSTYPE));
+ setsparam("TTY", ztrdup_metafy(ttystrname));
+ setsparam("VENDOR", ztrdup_metafy(VENDOR));
+ setsparam("ZSH_ARGZERO", ztrdup(posixzero));
+ setsparam("ZSH_VERSION", ztrdup_metafy(ZSH_VERSION));
+ setsparam("ZSH_PATCHLEVEL", ztrdup_metafy(ZSH_PATCHLEVEL));
+ setaparam("signals", sigptr = zalloc((SIGCOUNT+4) * sizeof(char *)));
+ for (t = sigs; (*sigptr++ = ztrdup_metafy(*t++)); );
+
+ noerrs = 0;
+}
+
+/* assign various functions used for non-special parameters */
+
+/**/
+mod_export void
+assigngetset(Param pm)
+{
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ pm->gsu.s = &stdscalar_gsu;
+ break;
+ case PM_INTEGER:
+ pm->gsu.i = &stdinteger_gsu;
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ pm->gsu.f = &stdfloat_gsu;
+ break;
+ case PM_ARRAY:
+ pm->gsu.a = &stdarray_gsu;
+ break;
+ case PM_HASHED:
+ pm->gsu.h = &stdhash_gsu;
+ break;
+ default:
+ DPUTS(1, "BUG: tried to create param node without valid flag");
+ break;
+ }
+}
+
+/* Create a parameter, so that it can be assigned to. Returns NULL if the *
+ * parameter already exists or can't be created, otherwise returns the *
+ * parameter node. If a parameter of the same name exists in an outer *
+ * scope, it is hidden by a newly created parameter. An already existing *
+ * parameter node at the current level may be `created' and returned *
+ * provided it is unset and not special. If the parameter can't be *
+ * created because it already exists, the PM_UNSET flag is cleared. */
+
+/**/
+mod_export Param
+createparam(char *name, int flags)
+{
+ Param pm, oldpm;
+
+ if (paramtab != realparamtab)
+ flags = (flags & ~PM_EXPORTED) | PM_HASHELEM;
+
+ if (name != nulstring) {
+ oldpm = (Param) (paramtab == realparamtab ?
+ /* gethashnode2() for direct table read */
+ gethashnode2(paramtab, name) :
+ paramtab->getnode(paramtab, name));
+
+ DPUTS(oldpm && oldpm->level > locallevel,
+ "BUG: old local parameter not deleted");
+ if (oldpm && (oldpm->level == locallevel || !(flags & PM_LOCAL))) {
+ if (isset(POSIXBUILTINS) && (oldpm->node.flags & PM_READONLY)) {
+ zerr("read-only variable: %s", name);
+ return NULL;
+ }
+ if ((oldpm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", name);
+ return NULL;
+ }
+ if (!(oldpm->node.flags & PM_UNSET) ||
+ (oldpm->node.flags & PM_SPECIAL) ||
+ /* POSIXBUILTINS horror: we need to retain 'export' flags */
+ (isset(POSIXBUILTINS) && (oldpm->node.flags & PM_EXPORTED))) {
+ oldpm->node.flags &= ~PM_UNSET;
+ if ((oldpm->node.flags & PM_SPECIAL) && oldpm->ename) {
+ Param altpm =
+ (Param) paramtab->getnode(paramtab, oldpm->ename);
+ if (altpm)
+ altpm->node.flags &= ~PM_UNSET;
+ }
+ return NULL;
+ }
+
+ pm = oldpm;
+ pm->base = pm->width = 0;
+ oldpm = pm->old;
+ } else {
+ pm = (Param) zshcalloc(sizeof *pm);
+ if ((pm->old = oldpm)) {
+ /*
+ * needed to avoid freeing oldpm, but we do take it
+ * out of the environment when it's hidden.
+ */
+ if (oldpm->env)
+ delenv(oldpm);
+ paramtab->removenode(paramtab, name);
+ }
+ paramtab->addnode(paramtab, ztrdup(name), pm);
+ }
+
+ if (isset(ALLEXPORT) && !(flags & PM_HASHELEM))
+ flags |= PM_EXPORTED;
+ } else {
+ pm = (Param) hcalloc(sizeof *pm);
+ pm->node.nam = nulstring;
+ }
+ pm->node.flags = flags & ~PM_LOCAL;
+
+ if(!(pm->node.flags & PM_SPECIAL))
+ assigngetset(pm);
+ return pm;
+}
+
+/* Empty dummy function for special hash parameters. */
+
+/**/
+static void
+shempty(void)
+{
+}
+
+/*
+ * Create a simple special hash parameter.
+ *
+ * This is for hashes added internally --- it's not possible to add
+ * special hashes from shell commands. It's currently used
+ * - by addparamdef() for special parameters in the zsh/parameter
+ * module
+ * - by ztie for special parameters tied to databases.
+ */
+
+/**/
+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;
+
+ /*
+ * If there's an old parameter, we'll put the new one at
+ * the current locallevel, so that the old parameter is
+ * exposed again after leaving the function. Otherwise,
+ * we'll leave it alone. Usually this means the parameter
+ * will stay in place until explicitly unloaded, however
+ * if the parameter was previously unset within a function
+ * we'll inherit the level of that function and follow the
+ * standard convention that the parameter remains local
+ * even if unset.
+ *
+ * These semantics are similar to those of a normal parameter set
+ * within a function without a local definition.
+ */
+ if (pm->old)
+ pm->level = locallevel;
+ 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
+ *
+ * If fakecopy is set, we are just saving the details of a special
+ * parameter. Otherwise, the result will be used as a real parameter
+ * and we need to do more work.
+ */
+
+/**/
+void
+copyparam(Param tpm, Param pm, int fakecopy)
+{
+ /*
+ * Note that tpm, into which we're copying, may not be in permanent
+ * storage. However, the values themselves are later used directly
+ * to set the parameter, so must be permanently allocated (in accordance
+ * with sets.?fn() usage).
+ */
+ tpm->node.flags = pm->node.flags;
+ tpm->base = pm->base;
+ tpm->width = pm->width;
+ tpm->level = pm->level;
+ if (!fakecopy)
+ tpm->node.flags &= ~PM_SPECIAL;
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ tpm->u.str = ztrdup(pm->gsu.s->getfn(pm));
+ break;
+ case PM_INTEGER:
+ tpm->u.val = pm->gsu.i->getfn(pm);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ tpm->u.dval = pm->gsu.f->getfn(pm);
+ break;
+ case PM_ARRAY:
+ tpm->u.arr = zarrdup(pm->gsu.a->getfn(pm));
+ break;
+ case PM_HASHED:
+ tpm->u.hash = copyparamtable(pm->gsu.h->getfn(pm), pm->node.nam);
+ break;
+ }
+ /*
+ * If the value is going to be passed as a real parameter (e.g. this is
+ * called from inside an associative array), we need the gets and sets
+ * functions to be useful.
+ *
+ * In this case we assume the saved parameter is not itself special,
+ * so we just use the standard functions. This is also why we switch off
+ * PM_SPECIAL.
+ */
+ if (!fakecopy)
+ assigngetset(tpm);
+}
+
+/* Return 1 if the string s is a valid identifier, else return 0. */
+
+/**/
+mod_export int
+isident(char *s)
+{
+ char *ss;
+
+ if (!*s) /* empty string is definitely not valid */
+ return 0;
+
+ if (idigit(*s)) {
+ /* If the first character is `s' is a digit, then all must be */
+ for (ss = ++s; *ss; ss++)
+ if (!idigit(*ss))
+ break;
+ } else {
+ /* Find the first character in `s' not in the iident type table */
+ ss = itype_end(s, IIDENT, 0);
+ }
+
+ /* If the next character is not [, then it is *
+ * definitely not a valid identifier. */
+ if (!*ss)
+ return 1;
+ if (s == ss)
+ return 0;
+ if (*ss != '[')
+ return 0;
+
+ /* Require balanced [ ] pairs with something between */
+ if (!(ss = parse_subscript(++ss, 1, ']')))
+ return 0;
+ untokenize(s);
+ return !ss[1];
+}
+
+/*
+ * Parse a single argument to a parameter subscript.
+ * The subscripts starts at *str; *str is updated (input/output)
+ *
+ * *inv is set to indicate if the subscript is reversed (output)
+ * v is the Value for the parameter being accessed (input; note
+ * v->isarr may be modified, and if v is a hash the parameter will
+ * be updated to the element of the hash)
+ * a2 is 1 if this is the second subscript of a range (input)
+ * *w is only set if we need to find the end of a word (input; should
+ * be set to 0 by the caller).
+ *
+ * The final two arguments are to support multibyte characters.
+ * If supplied they are set to the length of the character before
+ * the index position and the one at the index position. If
+ * multibyte characters are not in use they are set to 1 for
+ * consistency. Note they aren't fully handled if a2 is non-zero,
+ * since they aren't needed.
+ *
+ * Returns a raw offset into the value from the start or end (i.e.
+ * after the arithmetic for Meta and possible multibyte characters has
+ * been taken into account). This actually gives the offset *after*
+ * the character in question; subtract *prevcharlen if necessary.
+ */
+
+/**/
+static zlong
+getarg(char **str, int *inv, Value v, int a2, zlong *w,
+ int *prevcharlen, int *nextcharlen, int flags)
+{
+ int hasbeg = 0, word = 0, rev = 0, ind = 0, down = 0, l, i, ishash;
+ int keymatch = 0, needtok = 0, arglen, len, inpar = 0;
+ char *s = *str, *sep = NULL, *t, sav, *d, **ta, **p, *tt, c;
+ zlong num = 1, beg = 0, r = 0, quote_arg = 0;
+ Patprog pprog = NULL;
+
+ /*
+ * If in NO_EXEC mode, the parameters won't be set up properly,
+ * so just pretend everything is a hash for subscript parsing
+ */
+
+ ishash = (unset(EXECOPT) ||
+ (v->pm && PM_TYPE(v->pm->node.flags) == PM_HASHED));
+ if (prevcharlen)
+ *prevcharlen = 1;
+ if (nextcharlen)
+ *nextcharlen = 1;
+
+ /* first parse any subscription flags */
+ if (v->pm && (*s == '(' || *s == Inpar)) {
+ int escapes = 0;
+ int waste;
+ for (s++; *s != ')' && *s != Outpar && s != *str; s++) {
+ switch (*s) {
+ case 'r':
+ rev = 1;
+ keymatch = down = ind = 0;
+ break;
+ case 'R':
+ rev = down = 1;
+ keymatch = ind = 0;
+ break;
+ case 'k':
+ keymatch = ishash;
+ rev = 1;
+ down = ind = 0;
+ break;
+ case 'K':
+ keymatch = ishash;
+ rev = down = 1;
+ ind = 0;
+ break;
+ case 'i':
+ rev = ind = 1;
+ down = keymatch = 0;
+ break;
+ case 'I':
+ rev = ind = down = 1;
+ keymatch = 0;
+ break;
+ case 'w':
+ /* If the parameter is a scalar, then make subscription *
+ * work on a per-word basis instead of characters. */
+ word = 1;
+ break;
+ case 'f':
+ word = 1;
+ sep = "\n";
+ break;
+ case 'e':
+ quote_arg = 1;
+ break;
+ case 'n':
+ t = get_strarg(++s, &arglen);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ num = mathevalarg(s + arglen, &d);
+ if (!num)
+ num = 1;
+ *t = sav;
+ s = t + arglen - 1;
+ break;
+ case 'b':
+ hasbeg = 1;
+ t = get_strarg(++s, &arglen);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ if ((beg = mathevalarg(s + arglen, &d)) > 0)
+ beg--;
+ *t = sav;
+ s = t + arglen - 1;
+ break;
+ case 'p':
+ escapes = 1;
+ break;
+ case 's':
+ /* This gives the string that separates words *
+ * (for use with the `w' flag). */
+ t = get_strarg(++s, &arglen);
+ if (!*t)
+ goto flagerr;
+ sav = *t;
+ *t = '\0';
+ s += arglen;
+ sep = escapes ? getkeystring(s, &waste, GETKEYS_SEP, NULL)
+ : dupstring(s);
+ *t = sav;
+ s = t + arglen - 1;
+ break;
+ default:
+ flagerr:
+ num = 1;
+ word = rev = ind = down = keymatch = 0;
+ sep = NULL;
+ s = *str - 1;
+ }
+ }
+ if (s != *str)
+ s++;
+ }
+ if (num < 0) {
+ down = !down;
+ num = -num;
+ }
+ if (v->isarr & SCANPM_WANTKEYS)
+ *inv = (ind || !(v->isarr & SCANPM_WANTVALS));
+ else if (v->isarr & SCANPM_WANTVALS)
+ *inv = 0;
+ else {
+ if (v->isarr) {
+ if (ind) {
+ v->isarr |= SCANPM_WANTKEYS;
+ v->isarr &= ~SCANPM_WANTVALS;
+ } else if (rev)
+ v->isarr |= SCANPM_WANTVALS;
+ /*
+ * This catches the case where we are using "k" (rather
+ * than "K") on a hash.
+ */
+ if (!down && keymatch && ishash)
+ v->isarr &= ~SCANPM_MATCHMANY;
+ }
+ *inv = ind;
+ }
+
+ for (t = s, i = 0;
+ (c = *t) &&
+ ((c != Outbrack && (ishash || c != ',')) || i || inpar);
+ t++) {
+ /* Untokenize inull() except before brackets and double-quotes */
+ if (inull(c)) {
+ c = t[1];
+ if (c == '[' || c == ']' ||
+ c == '(' || c == ')' ||
+ c == '{' || c == '}') {
+ /* This test handles nested subscripts in hash keys */
+ if (ishash && i)
+ *t = ztokens[*t - Pound];
+ needtok = 1;
+ ++t;
+ } else if (c != '"')
+ *t = ztokens[*t - Pound];
+ continue;
+ }
+ /* Inbrack and Outbrack are probably never found here ... */
+ if (c == '[' || c == Inbrack)
+ i++;
+ else if (c == ']' || c == Outbrack)
+ i--;
+ if (c == '(' || c == Inpar)
+ inpar++;
+ else if (c == ')' || c == Outpar)
+ inpar--;
+ if (ispecial(c))
+ needtok = 1;
+ }
+ if (!c)
+ return 0;
+ *str = tt = t;
+
+ /*
+ * If in NO_EXEC mode, the parameters won't be set up properly,
+ * so there's no additional sanity checking we can do.
+ * Just return 0 now.
+ */
+ if (unset(EXECOPT))
+ return 0;
+
+ s = dupstrpfx(s, t - s);
+
+ /* If we're NOT reverse subscripting, strip the inull()s so brackets *
+ * are not backslashed after parsestr(). Otherwise leave them alone *
+ * so that the brackets will be escaped when we patcompile() or when *
+ * subscript arithmetic is performed (for nested subscripts). */
+ if (ishash && (keymatch || !rev))
+ remnulargs(s);
+ if (needtok) {
+ s = dupstring(s);
+ if (parsestr(&s))
+ return 0;
+ singsub(&s);
+ } else if (rev)
+ remnulargs(s); /* This is probably always a no-op, but ... */
+ if (!rev) {
+ if (ishash) {
+ HashTable ht = v->pm->gsu.h->getfn(v->pm);
+ if (!ht) {
+ if (flags & SCANPM_CHECKING)
+ return 0;
+ ht = newparamtable(17, v->pm->node.nam);
+ v->pm->gsu.h->setfn(v->pm, ht);
+ }
+ untokenize(s);
+ if (!(v->pm = (Param) ht->getnode(ht, s))) {
+ HashTable tht = paramtab;
+ paramtab = ht;
+ v->pm = createparam(s, PM_SCALAR|PM_UNSET);
+ paramtab = tht;
+ }
+ v->isarr = (*inv ? SCANPM_WANTINDEX : 0);
+ v->start = 0;
+ *inv = 0; /* We've already obtained the "index" (key) */
+ *w = v->end = -1;
+ r = isset(KSHARRAYS) ? 1 : 0;
+ } else {
+ r = mathevalarg(s, &s);
+ if (isset(KSHARRAYS) && r >= 0)
+ r++;
+ }
+ if (word && !v->isarr) {
+ s = t = getstrvalue(v);
+ i = wordcount(s, sep, 0);
+ if (r < 0)
+ r += i + 1;
+ if (r < 1)
+ r = 1;
+ if (r > i)
+ r = i;
+ if (!s || !*s)
+ return 0;
+ while ((d = findword(&s, sep)) && --r);
+ if (!d)
+ return 0;
+
+ if (!a2 && *tt != ',')
+ *w = (zlong)(s - t);
+
+ return (a2 ? s : d + 1) - t;
+ } else if (!v->isarr && !word) {
+ int lastcharlen = 1;
+ s = getstrvalue(v);
+ /*
+ * Note for the confused (= pws): the index r we
+ * have so far is that specified by the user. The value
+ * passed back is an offset from the start or end of
+ * the string. Hence it needs correcting at least
+ * for Meta characters and maybe for multibyte characters.
+ */
+ if (r > 0) {
+ zlong nchars = r;
+
+ MB_METACHARINIT();
+ for (t = s; nchars && *t; nchars--)
+ t += (lastcharlen = MB_METACHARLEN(t));
+ /* for consistency, keep any remainder off the end */
+ r = (zlong)(t - s) + nchars;
+ if (prevcharlen && !nchars /* ignore if off the end */)
+ *prevcharlen = lastcharlen;
+ if (nextcharlen && *t)
+ *nextcharlen = MB_METACHARLEN(t);
+ } else if (r == 0) {
+ if (prevcharlen)
+ *prevcharlen = 0;
+ if (nextcharlen && *s) {
+ MB_METACHARINIT();
+ *nextcharlen = MB_METACHARLEN(s);
+ }
+ } else {
+ zlong nchars = (zlong)MB_METASTRLEN(s) + r;
+
+ if (nchars < 0) {
+ /* make sure this isn't valid as a raw pointer */
+ r -= (zlong)strlen(s);
+ } else {
+ MB_METACHARINIT();
+ for (t = s; nchars && *t; nchars--)
+ t += (lastcharlen = MB_METACHARLEN(t));
+ r = - (zlong)strlen(t); /* keep negative */
+ if (prevcharlen)
+ *prevcharlen = lastcharlen;
+ if (nextcharlen && *t)
+ *nextcharlen = MB_METACHARLEN(t);
+ }
+ }
+ }
+ } else {
+ if (!v->isarr && !word && !quote_arg) {
+ l = strlen(s);
+ if (a2) {
+ if (!l || *s != '*') {
+ d = (char *) hcalloc(l + 2);
+ *d = '*';
+ strcpy(d + 1, s);
+ s = d;
+ }
+ } else {
+ if (!l || s[l - 1] != '*' || (l > 1 && s[l - 2] == '\\')) {
+ d = (char *) hcalloc(l + 2);
+ strcpy(d, s);
+ strcat(d, "*");
+ s = d;
+ }
+ }
+ }
+ if (!keymatch) {
+ if (quote_arg) {
+ untokenize(s);
+ /* Scalar (e) needs implicit asterisk tokens */
+ if (!v->isarr && !word) {
+ l = strlen(s);
+ d = (char *) hcalloc(l + 2);
+ if (a2) {
+ *d = Star;
+ strcpy(d + 1, s);
+ } else {
+ strcpy(d, s);
+ d[l] = Star;
+ d[l + 1] = '\0';
+ }
+ s = d;
+ }
+ } else
+ tokenize(s);
+ remnulargs(s);
+ pprog = patcompile(s, 0, NULL);
+ } else
+ pprog = NULL;
+
+ if (v->isarr) {
+ if (ishash) {
+ scanprog = pprog;
+ scanstr = s;
+ if (keymatch)
+ v->isarr |= SCANPM_KEYMATCH;
+ else {
+ if (!pprog)
+ return 1;
+ if (ind)
+ v->isarr |= SCANPM_MATCHKEY;
+ else
+ v->isarr |= SCANPM_MATCHVAL;
+ }
+ if (down)
+ v->isarr |= SCANPM_MATCHMANY;
+ if ((ta = getvaluearr(v)) &&
+ (*ta || ((v->isarr & SCANPM_MATCHMANY) &&
+ (v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
+ SCANPM_KEYMATCH))))) {
+ *inv = (v->flags & VALFLAG_INV) ? 1 : 0;
+ *w = v->end;
+ scanprog = NULL;
+ return 1;
+ }
+ scanprog = NULL;
+ } else
+ ta = getarrvalue(v);
+ if (!ta || !*ta)
+ return !down;
+ len = arrlen(ta);
+ if (beg < 0)
+ beg += len;
+ if (down) {
+ if (beg < 0)
+ return 0;
+ } else if (beg >= len)
+ return len + 1;
+ if (beg >= 0 && beg < len) {
+ if (down) {
+ if (!hasbeg)
+ beg = len - 1;
+ for (r = 1 + beg, p = ta + beg; p >= ta; r--, p--) {
+ if (pprog && pattry(pprog, *p) && !--num)
+ return r;
+ }
+ } else
+ for (r = 1 + beg, p = ta + beg; *p; r++, p++)
+ if (pprog && pattry(pprog, *p) && !--num)
+ return r;
+ }
+ } else if (word) {
+ ta = sepsplit(d = s = getstrvalue(v), sep, 1, 1);
+ len = arrlen(ta);
+ if (beg < 0)
+ beg += len;
+ if (down) {
+ if (beg < 0)
+ return 0;
+ } else if (beg >= len)
+ return len + 1;
+ if (beg >= 0 && beg < len) {
+ if (down) {
+ if (!hasbeg)
+ beg = len - 1;
+ for (r = 1 + beg, p = ta + beg; p >= ta; p--, r--)
+ if (pprog && pattry(pprog, *p) && !--num)
+ break;
+ if (p < ta)
+ return 0;
+ } else {
+ for (r = 1 + beg, p = ta + beg; *p; r++, p++)
+ if (pprog && pattry(pprog, *p) && !--num)
+ break;
+ if (!*p)
+ return 0;
+ }
+ }
+ if (a2)
+ r++;
+ for (i = 0; (t = findword(&d, sep)) && *t; i++)
+ if (!--r) {
+ r = (zlong)(t - s + (a2 ? -1 : 1));
+ if (!a2 && *tt != ',')
+ *w = r + strlen(ta[i]) - 1;
+ return r;
+ }
+ return a2 ? -1 : 0;
+ } else {
+ /* Searching characters */
+ int slen;
+ d = getstrvalue(v);
+ if (!d || !*d)
+ return 0;
+ /*
+ * beg and len are character counts, not raw offsets.
+ * Remember we need to return a raw offset.
+ */
+ len = MB_METASTRLEN(d);
+ slen = strlen(d);
+ if (beg < 0)
+ beg += len;
+ MB_METACHARINIT();
+ if (beg >= 0 && beg < len) {
+ char *de = d + slen;
+
+ if (a2) {
+ /*
+ * Second argument: we don't need to
+ * handle prevcharlen or nextcharlen, but
+ * we do need to handle characters appropriately.
+ */
+ if (down) {
+ int nmatches = 0;
+ char *lastpos = NULL;
+
+ if (!hasbeg)
+ beg = len;
+
+ /*
+ * See below: we have to move forward,
+ * but need to count from the end.
+ */
+ for (t = d, r = 0; r <= beg; r++) {
+ sav = *t;
+ *t = '\0';
+ if (pprog && pattry(pprog, d)) {
+ nmatches++;
+ lastpos = t;
+ }
+ *t = sav;
+ if (t == de)
+ break;
+ t += MB_METACHARLEN(t);
+ }
+
+ if (nmatches >= num) {
+ if (num > 1) {
+ nmatches -= num;
+ MB_METACHARINIT();
+ for (t = d, r = 0; ; r++) {
+ sav = *t;
+ *t = '\0';
+ if (pprog && pattry(pprog, d) &&
+ nmatches-- == 0) {
+ lastpos = t;
+ *t = sav;
+ break;
+ }
+ *t = sav;
+ t += MB_METACHARLEN(t);
+ }
+ }
+ /* else lastpos is already OK */
+
+ return lastpos - d;
+ }
+ } else {
+ /*
+ * This handling of the b flag
+ * gives odd results, but this is the
+ * way it's always worked.
+ */
+ for (t = d; beg && t <= de; beg--)
+ t += MB_METACHARLEN(t);
+ for (;;) {
+ sav = *t;
+ *t = '\0';
+ if (pprog && pattry(pprog, d) && !--num) {
+ *t = sav;
+ /*
+ * This time, don't increment
+ * pointer, since it's already
+ * after everything we matched.
+ */
+ return t - d;
+ }
+ *t = sav;
+ if (t == de)
+ break;
+ t += MB_METACHARLEN(t);
+ }
+ }
+ } else {
+ /*
+ * First argument: this is the only case
+ * where we need prevcharlen and nextcharlen.
+ */
+ int lastcharlen;
+
+ if (down) {
+ int nmatches = 0;
+ char *lastpos = NULL;
+
+ if (!hasbeg)
+ beg = len;
+
+ /*
+ * We can only move forward through
+ * multibyte strings, so record the
+ * matches.
+ * Unfortunately the count num works
+ * from the end, so it's easy to get the
+ * last one but we need to repeat if
+ * we want another one.
+ */
+ for (t = d, r = 0; r <= beg; r++) {
+ if (pprog && pattry(pprog, t)) {
+ nmatches++;
+ lastpos = t;
+ }
+ if (t == de)
+ break;
+ t += MB_METACHARLEN(t);
+ }
+
+ if (nmatches >= num) {
+ if (num > 1) {
+ /*
+ * Need to start again and repeat
+ * to get the right match.
+ */
+ nmatches -= num;
+ MB_METACHARINIT();
+ for (t = d, r = 0; ; r++) {
+ if (pprog && pattry(pprog, t) &&
+ nmatches-- == 0) {
+ lastpos = t;
+ break;
+ }
+ t += MB_METACHARLEN(t);
+ }
+ }
+ /* else lastpos is already OK */
+
+ /* return pointer after matched char */
+ lastpos +=
+ (lastcharlen = MB_METACHARLEN(lastpos));
+ if (prevcharlen)
+ *prevcharlen = lastcharlen;
+ if (nextcharlen)
+ *nextcharlen = MB_METACHARLEN(lastpos);
+ return lastpos - d;
+ }
+
+ for (r = beg + 1, t = d + beg; t >= d; r--, t--) {
+ if (pprog && pattry(pprog, t) &&
+ !--num)
+ return r;
+ }
+ } else {
+ for (t = d; beg && t <= de; beg--)
+ t += MB_METACHARLEN(t);
+ for (;;) {
+ if (pprog && pattry(pprog, t) && !--num) {
+ /* return pointer after matched char */
+ t += (lastcharlen = MB_METACHARLEN(t));
+ if (prevcharlen)
+ *prevcharlen = lastcharlen;
+ if (nextcharlen)
+ *nextcharlen = MB_METACHARLEN(t);
+ return t - d;
+ }
+ if (t == de)
+ break;
+ t += MB_METACHARLEN(t);
+ }
+ }
+ }
+ }
+ return down ? 0 : slen + 1;
+ }
+ }
+ return r;
+}
+
+/*
+ * Parse a subscript.
+ *
+ * pptr: In/Out parameter. On entry, *ptr points to a "[foo]" string. On exit
+ * it will point one past the closing bracket.
+ *
+ * v: In/Out parameter. Its .start and .end members (at least) will be updated
+ * with the parsed indices.
+ *
+ * flags: can be either SCANPM_DQUOTED or zero. Other bits are not used.
+ */
+
+/**/
+int
+getindex(char **pptr, Value v, int flags)
+{
+ int start, end, inv = 0;
+ char *s = *pptr, *tbrack;
+
+ *s++ = '[';
+ /* Error handled after untokenizing */
+ s = parse_subscript(s, flags & SCANPM_DQUOTED, ']');
+ /* Now we untokenize everything except inull() markers so we can check *
+ * for the '*' and '@' special subscripts. The inull()s are removed *
+ * in getarg() after we know whether we're doing reverse indexing. */
+ for (tbrack = *pptr + 1; *tbrack && tbrack != s; tbrack++) {
+ if (inull(*tbrack) && !*++tbrack)
+ break;
+ if (itok(*tbrack)) /* Need to check for Nularg here? */
+ *tbrack = ztokens[*tbrack - Pound];
+ }
+ /* If we reached the end of the string (s == NULL) we have an error */
+ if (*tbrack)
+ *tbrack = Outbrack;
+ else {
+ zerr("invalid subscript");
+ *pptr = tbrack;
+ return 1;
+ }
+ s = *pptr + 1;
+ if ((s[0] == '*' || s[0] == '@') && s + 1 == tbrack) {
+ if ((v->isarr || IS_UNSET_VALUE(v)) && s[0] == '@')
+ v->isarr |= SCANPM_ISVAR_AT;
+ v->start = 0;
+ v->end = -1;
+ s += 2;
+ } else {
+ zlong we = 0, dummy;
+ int startprevlen, startnextlen;
+
+ start = getarg(&s, &inv, v, 0, &we, &startprevlen, &startnextlen,
+ flags);
+
+ if (inv) {
+ if (!v->isarr && start != 0) {
+ char *t, *p;
+ t = getstrvalue(v);
+ /*
+ * Note for the confused (= pws): this is an inverse
+ * offset so at this stage we need to convert from
+ * the immediate offset into the value that we have
+ * into a logical character position.
+ */
+ if (start > 0) {
+ int nstart = 0;
+ char *target = t + start - startprevlen;
+
+ p = t;
+ MB_METACHARINIT();
+ while (*p) {
+ /*
+ * move up characters, counting how many we
+ * found
+ */
+ p += MB_METACHARLEN(p);
+ if (p < target)
+ nstart++;
+ else {
+ if (p == target)
+ nstart++;
+ else
+ p = target; /* pretend we hit exactly */
+ break;
+ }
+ }
+ /* if start was too big, keep the difference */
+ start = nstart + (target - p) + 1;
+ } else {
+ zlong startoff = start + strlen(t);
+#ifdef DEBUG
+ dputs("BUG: can't have negative inverse offsets???");
+#endif
+ if (startoff < 0) {
+ /* invalid: keep index but don't dereference */
+ start = startoff;
+ } else {
+ /* find start in full characters */
+ MB_METACHARINIT();
+ for (p = t; p < t + startoff;)
+ p += MB_METACHARLEN(p);
+ start = - MB_METASTRLEN(p);
+ }
+ }
+ }
+ if (start > 0 && (isset(KSHARRAYS) || (v->pm->node.flags & PM_HASHED)))
+ start--;
+ if (v->isarr != SCANPM_WANTINDEX) {
+ v->flags |= VALFLAG_INV;
+ v->isarr = 0;
+ v->start = start;
+ v->end = start + 1;
+ }
+ if (*s == ',') {
+ zerr("invalid subscript");
+ *tbrack = ']';
+ *pptr = tbrack+1;
+ return 1;
+ }
+ if (s == tbrack)
+ s++;
+ } else {
+ int com;
+
+ if ((com = (*s == ','))) {
+ s++;
+ end = getarg(&s, &inv, v, 1, &dummy, NULL, NULL, flags);
+ } else {
+ end = we ? we : start;
+ }
+ if (start != end)
+ com = 1;
+ /*
+ * Somehow the logic sometimes forces us to use the previous
+ * or next character to what we would expect, which is
+ * why we had to calculate them in getarg().
+ */
+ if (start > 0)
+ start -= startprevlen;
+ else if (start == 0 && end == 0)
+ {
+ /*
+ * Strictly, this range is entirely off the
+ * start of the available index range.
+ * This can't happen with KSH_ARRAYS; we already
+ * altered the start index in getarg().
+ * Are we being strict?
+ */
+ if (isset(KSHZEROSUBSCRIPT)) {
+ /*
+ * We're not.
+ * Treat this as accessing the first element of the
+ * array.
+ */
+ end = startnextlen;
+ } else {
+ /*
+ * We are. Flag that this range is invalid
+ * for setting elements. Set the indexes
+ * to a range that returns empty for other accesses.
+ */
+ v->flags |= VALFLAG_EMPTY;
+ start = -1;
+ com = 1;
+ }
+ }
+ if (s == tbrack) {
+ s++;
+ if (v->isarr && !com &&
+ (!(v->isarr & SCANPM_MATCHMANY) ||
+ !(v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
+ SCANPM_KEYMATCH))))
+ v->isarr = 0;
+ v->start = start;
+ v->end = end;
+ } else
+ s = *pptr;
+ }
+ }
+ *tbrack = ']';
+ *pptr = s;
+ return 0;
+}
+
+
+/**/
+mod_export Value
+getvalue(Value v, char **pptr, int bracks)
+{
+ return fetchvalue(v, pptr, bracks, 0);
+}
+
+/**/
+mod_export Value
+fetchvalue(Value v, char **pptr, int bracks, int flags)
+{
+ char *s, *t, *ie;
+ char sav, c;
+ int ppar = 0;
+
+ s = t = *pptr;
+
+ if (idigit(c = *s)) {
+ if (bracks >= 0)
+ ppar = zstrtol(s, &s, 10);
+ else
+ ppar = *s++ - '0';
+ }
+ else if ((ie = itype_end(s, IIDENT, 0)) != s)
+ s = ie;
+ else if (c == Quest)
+ *s++ = '?';
+ else if (c == Pound)
+ *s++ = '#';
+ else if (c == String)
+ *s++ = '$';
+ else if (c == Qstring)
+ *s++ = '$';
+ else if (c == Star)
+ *s++ = '*';
+ else if (IS_DASH(c))
+ *s++ = '-';
+ else if (c == '#' || c == '?' || c == '$' ||
+ c == '!' || c == '@' || c == '*')
+ s++;
+ else
+ return NULL;
+
+ if ((sav = *s))
+ *s = '\0';
+ if (ppar) {
+ if (v)
+ memset(v, 0, sizeof(*v));
+ else
+ v = (Value) hcalloc(sizeof *v);
+ v->pm = argvparam;
+ v->flags = 0;
+ v->start = ppar - 1;
+ v->end = ppar;
+ if (sav)
+ *s = sav;
+ } else {
+ Param pm;
+ int isvarat;
+
+ isvarat = (t[0] == '@' && !t[1]);
+ pm = (Param) paramtab->getnode(paramtab, *t == '0' ? "0" : t);
+ if (sav)
+ *s = sav;
+ *pptr = s;
+ if (!pm || (pm->node.flags & PM_UNSET))
+ return NULL;
+ if (v)
+ memset(v, 0, sizeof(*v));
+ else
+ v = (Value) hcalloc(sizeof *v);
+ if (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) {
+ /* Overload v->isarr as the flag bits for hashed arrays. */
+ v->isarr = flags | (isvarat ? SCANPM_ISVAR_AT : 0);
+ /* If no flags were passed, we need something to represent *
+ * `true' yet differ from an explicit WANTVALS. Use a *
+ * special flag for this case. */
+ if (!v->isarr)
+ v->isarr = SCANPM_ARRONLY;
+ }
+ v->pm = pm;
+ v->flags = 0;
+ v->start = 0;
+ v->end = -1;
+ if (bracks > 0 && (*s == '[' || *s == Inbrack)) {
+ if (getindex(&s, v, flags)) {
+ *pptr = s;
+ return v;
+ }
+ } else if (!(flags & SCANPM_ASSIGNING) && v->isarr &&
+ itype_end(t, IIDENT, 1) != t && isset(KSHARRAYS))
+ v->end = 1, v->isarr = 0;
+ }
+ if (!bracks && *s)
+ return NULL;
+ *pptr = s;
+#if 0
+ /*
+ * Check for large subscripts that might be erroneous.
+ * This code is too gross in several ways:
+ * - the limit is completely arbitrary
+ * - the test vetoes operations on existing arrays
+ * - it's not at all clear a general test on large arrays of
+ * this kind is any use.
+ *
+ * Until someone comes up with workable replacement code it's
+ * therefore commented out.
+ */
+ if (v->start > MAX_ARRLEN) {
+ zerr("subscript too %s: %d", "big", v->start + !isset(KSHARRAYS));
+ return NULL;
+ }
+ if (v->start < -MAX_ARRLEN) {
+ zerr("subscript too %s: %d", "small", v->start);
+ return NULL;
+ }
+ if (v->end > MAX_ARRLEN+1) {
+ zerr("subscript too %s: %d", "big", v->end - !!isset(KSHARRAYS));
+ return NULL;
+ }
+ if (v->end < -MAX_ARRLEN) {
+ zerr("subscript too %s: %d", "small", v->end);
+ return NULL;
+ }
+#endif
+ return v;
+}
+
+/**/
+mod_export char *
+getstrvalue(Value v)
+{
+ char *s, **ss;
+ char buf[BDIGBUFSIZE];
+ int len;
+
+ if (!v)
+ return hcalloc(1);
+
+ if ((v->flags & VALFLAG_INV) && !(v->pm->node.flags & PM_HASHED)) {
+ sprintf(buf, "%d", v->start);
+ s = dupstring(buf);
+ return s;
+ }
+
+ switch(PM_TYPE(v->pm->node.flags)) {
+ case PM_HASHED:
+ /* (!v->isarr) should be impossible unless emulating ksh */
+ if (!v->isarr && EMULATION(EMULATE_KSH)) {
+ s = dupstring("[0]");
+ if (getindex(&s, v, 0) == 0)
+ s = getstrvalue(v);
+ return s;
+ } /* else fall through */
+ case PM_ARRAY:
+ ss = getvaluearr(v);
+ if (v->isarr)
+ s = sepjoin(ss, NULL, 1);
+ else {
+ if (v->start < 0)
+ v->start += arrlen(ss);
+ s = (arrlen_le(ss, v->start) || v->start < 0) ?
+ (char *) hcalloc(1) : ss[v->start];
+ }
+ return s;
+ case PM_INTEGER:
+ convbase(buf, v->pm->gsu.i->getfn(v->pm), v->pm->base);
+ s = dupstring(buf);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ s = convfloat(v->pm->gsu.f->getfn(v->pm),
+ v->pm->base, v->pm->node.flags, NULL);
+ break;
+ case PM_SCALAR:
+ s = v->pm->gsu.s->getfn(v->pm);
+ break;
+ default:
+ s = "";
+ DPUTS(1, "BUG: param node without valid type");
+ break;
+ }
+
+ if (v->flags & VALFLAG_SUBST) {
+ if (v->pm->node.flags & (PM_LEFT|PM_RIGHT_B|PM_RIGHT_Z)) {
+ unsigned int fwidth = v->pm->width ? v->pm->width : MB_METASTRLEN(s);
+ switch (v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
+ char *t, *tend;
+ unsigned int t0;
+
+ case PM_LEFT:
+ case PM_LEFT | PM_RIGHT_Z:
+ t = s;
+ if (v->pm->node.flags & PM_RIGHT_Z)
+ while (*t == '0')
+ t++;
+ else
+ while (iblank(*t))
+ t++;
+ MB_METACHARINIT();
+ for (tend = t, t0 = 0; t0 < fwidth && *tend; t0++)
+ tend += MB_METACHARLEN(tend);
+ /*
+ * t0 is the number of characters from t used,
+ * hence (fwidth - t0) is the number of padding
+ * characters. fwidth is a misnomer: we use
+ * character counts, not character widths.
+ *
+ * (tend - t) is the number of bytes we need
+ * to get fwidth characters or the entire string;
+ * the characters may be multiple bytes.
+ */
+ fwidth -= t0; /* padding chars remaining */
+ t0 = tend - t; /* bytes to copy from string */
+ s = (char *) hcalloc(t0 + fwidth + 1);
+ memcpy(s, t, t0);
+ if (fwidth)
+ memset(s + t0, ' ', fwidth);
+ s[t0 + fwidth] = '\0';
+ break;
+ case PM_RIGHT_B:
+ case PM_RIGHT_Z:
+ case PM_RIGHT_Z | PM_RIGHT_B:
+ {
+ int zero = 1;
+ /* Calculate length in possibly multibyte chars */
+ unsigned int charlen = MB_METASTRLEN(s);
+
+ if (charlen < fwidth) {
+ char *valprefend = s;
+ int preflen;
+ if (v->pm->node.flags & PM_RIGHT_Z) {
+ /*
+ * This is a documented feature: when deciding
+ * whether to pad with zeroes, ignore
+ * leading blanks already in the value;
+ * only look for numbers after that.
+ * Not sure how useful this really is.
+ * It's certainly confusing to code around.
+ */
+ for (t = s; iblank(*t); t++)
+ ;
+ /*
+ * Allow padding after initial minus
+ * for numeric variables.
+ */
+ if ((v->pm->node.flags &
+ (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) &&
+ *t == '-')
+ t++;
+ /*
+ * Allow padding after initial 0x or
+ * base# for integer variables.
+ */
+ if (v->pm->node.flags & PM_INTEGER) {
+ if (isset(CBASES) &&
+ t[0] == '0' && t[1] == 'x')
+ t += 2;
+ else if ((valprefend = strchr(t, '#')))
+ t = valprefend + 1;
+ }
+ valprefend = t;
+ if (!*t)
+ zero = 0;
+ else if (v->pm->node.flags &
+ (PM_INTEGER|PM_EFLOAT|PM_FFLOAT)) {
+ /* zero always OK */
+ } else if (!idigit(*t))
+ zero = 0;
+ }
+ /* number of characters needed for padding */
+ fwidth -= charlen;
+ /* bytes from original string */
+ t0 = strlen(s);
+ t = (char *) hcalloc(fwidth + t0 + 1);
+ /* prefix guaranteed to be single byte chars */
+ preflen = valprefend - s;
+ memset(t + preflen,
+ (((v->pm->node.flags & PM_RIGHT_B)
+ || !zero) ? ' ' : '0'), fwidth);
+ /*
+ * Copy - or 0x or base# before any padding
+ * zeroes.
+ */
+ if (preflen)
+ memcpy(t, s, preflen);
+ memcpy(t + preflen + fwidth,
+ valprefend, t0 - preflen);
+ t[fwidth + t0] = '\0';
+ s = t;
+ } else {
+ /* Need to skip (charlen - fwidth) chars */
+ for (t0 = charlen - fwidth; t0; t0--)
+ s += MB_METACHARLEN(s);
+ }
+ }
+ break;
+ }
+ }
+ switch (v->pm->node.flags & (PM_LOWER | PM_UPPER)) {
+ case PM_LOWER:
+ s = casemodify(s, CASMOD_LOWER);
+ break;
+ case PM_UPPER:
+ s = casemodify(s, CASMOD_UPPER);
+ break;
+ }
+ }
+ if (v->start == 0 && v->end == -1)
+ return s;
+
+ len = strlen(s);
+ if (v->start < 0) {
+ v->start += len;
+ if (v->start < 0)
+ v->start = 0;
+ }
+ if (v->end < 0) {
+ v->end += len;
+ if (v->end >= 0) {
+ char *eptr = s + v->end;
+ if (*eptr)
+ v->end += MB_METACHARLEN(eptr);
+ }
+ }
+
+ s = (v->start > len) ? dupstring("") :
+ dupstring_wlen(s + v->start, len - v->start);
+
+ if (v->end <= v->start)
+ s[0] = '\0';
+ else if (v->end - v->start <= len - v->start)
+ s[v->end - v->start] = '\0';
+
+ return s;
+}
+
+static char *nular[] = {"", NULL};
+
+/**/
+mod_export char **
+getarrvalue(Value v)
+{
+ char **s;
+
+ if (!v)
+ return arrdup(nular);
+ else if (IS_UNSET_VALUE(v))
+ return arrdup(&nular[1]);
+ if (v->flags & VALFLAG_INV) {
+ char buf[DIGBUFSIZE];
+
+ s = arrdup(nular);
+ sprintf(buf, "%d", v->start);
+ s[0] = dupstring(buf);
+ return s;
+ }
+ s = getvaluearr(v);
+ if (v->start == 0 && v->end == -1)
+ return s;
+ if (v->start < 0)
+ v->start += arrlen(s);
+ if (v->end < 0)
+ v->end += arrlen(s) + 1;
+
+ /* Null if 1) array too short, 2) index still negative */
+ if (v->end <= v->start) {
+ s = arrdup_max(nular, 0);
+ }
+ else if (v->start < 0) {
+ s = arrdup_max(nular, 1);
+ }
+ else if (arrlen_le(s, v->start)) {
+ /* Handle $ary[i,i] consistently for any $i > $#ary
+ * and $ary[i,j] consistently for any $j > $i > $#ary
+ */
+ s = arrdup_max(nular, v->end - (v->start + 1));
+ }
+ else {
+ /* Copy to a point before the end of the source array:
+ * arrdup_max will copy at most v->end - v->start elements,
+ * starting from v->start element. Original code said:
+ * s[v->end - v->start] = NULL
+ * which means that there are exactly the same number of
+ * elements as the value of the above *0-based* index.
+ */
+ s = arrdup_max(s + v->start, v->end - v->start);
+ }
+
+ return s;
+}
+
+/**/
+mod_export zlong
+getintvalue(Value v)
+{
+ if (!v)
+ return 0;
+ if (v->flags & VALFLAG_INV)
+ return v->start;
+ if (v->isarr) {
+ char **arr = getarrvalue(v);
+ if (arr) {
+ char *scal = sepjoin(arr, NULL, 1);
+ return mathevali(scal);
+ } else
+ return 0;
+ }
+ if (PM_TYPE(v->pm->node.flags) == PM_INTEGER)
+ return v->pm->gsu.i->getfn(v->pm);
+ if (v->pm->node.flags & (PM_EFLOAT|PM_FFLOAT))
+ return (zlong)v->pm->gsu.f->getfn(v->pm);
+ return mathevali(getstrvalue(v));
+}
+
+/**/
+mnumber
+getnumvalue(Value v)
+{
+ mnumber mn;
+ mn.type = MN_INTEGER;
+
+
+ if (!v) {
+ mn.u.l = 0;
+ } else if (v->flags & VALFLAG_INV) {
+ mn.u.l = v->start;
+ } else if (v->isarr) {
+ char **arr = getarrvalue(v);
+ if (arr) {
+ char *scal = sepjoin(arr, NULL, 1);
+ return matheval(scal);
+ } else
+ mn.u.l = 0;
+ } else if (PM_TYPE(v->pm->node.flags) == PM_INTEGER) {
+ mn.u.l = v->pm->gsu.i->getfn(v->pm);
+ } else if (v->pm->node.flags & (PM_EFLOAT|PM_FFLOAT)) {
+ mn.type = MN_FLOAT;
+ mn.u.d = v->pm->gsu.f->getfn(v->pm);
+ } else
+ return matheval(getstrvalue(v));
+ return mn;
+}
+
+/**/
+void
+export_param(Param pm)
+{
+ char buf[BDIGBUFSIZE], *val;
+
+ if (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)) {
+#if 0 /* Requires changes elsewhere in params.c and builtin.c */
+ if (EMULATION(EMULATE_KSH) /* isset(KSHARRAYS) */) {
+ struct value v;
+ v.isarr = 1;
+ v.flags = 0;
+ v.start = 0;
+ v.end = -1;
+ val = getstrvalue(&v);
+ } else
+#endif
+ return;
+ } else if (PM_TYPE(pm->node.flags) == PM_INTEGER)
+ convbase(val = buf, pm->gsu.i->getfn(pm), pm->base);
+ else if (pm->node.flags & (PM_EFLOAT|PM_FFLOAT))
+ val = convfloat(pm->gsu.f->getfn(pm), pm->base,
+ pm->node.flags, NULL);
+ else
+ val = pm->gsu.s->getfn(pm);
+
+ addenv(pm, val);
+}
+
+/**/
+mod_export void
+setstrvalue(Value v, char *val)
+{
+ assignstrvalue(v, val, 0);
+}
+
+/**/
+mod_export void
+assignstrvalue(Value v, char *val, int flags)
+{
+ if (unset(EXECOPT))
+ return;
+ if (v->pm->node.flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ if ((v->pm->node.flags & PM_HASHED) &&
+ (v->isarr & (SCANPM_MATCHMANY|SCANPM_ARRONLY))) {
+ zerr("%s: attempt to set slice of associative array", v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ if (v->flags & VALFLAG_EMPTY) {
+ zerr("%s: assignment to invalid subscript range", v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ v->pm->node.flags &= ~PM_UNSET;
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_SCALAR:
+ if (v->start == 0 && v->end == -1) {
+ v->pm->gsu.s->setfn(v->pm, val);
+ if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+ !v->pm->width)
+ v->pm->width = strlen(val);
+ } else {
+ char *z, *x;
+ int zlen, vlen, newsize;
+
+ z = v->pm->gsu.s->getfn(v->pm);
+ zlen = strlen(z);
+
+ if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS))
+ v->start--, v->end--;
+ if (v->start < 0) {
+ v->start += zlen;
+ if (v->start < 0)
+ v->start = 0;
+ }
+ if (v->start > zlen)
+ v->start = zlen;
+ if (v->end < 0) {
+ v->end += zlen;
+ if (v->end < 0) {
+ v->end = 0;
+ } else if (v->end >= zlen) {
+ v->end = zlen;
+ } else {
+#ifdef MULTIBYTE_SUPPORT
+ if (isset(MULTIBYTE)) {
+ v->end += MB_METACHARLEN(z + v->end);
+ } else {
+ v->end++;
+ }
+#else
+ v->end++;
+#endif
+ }
+ }
+ else if (v->end > zlen)
+ v->end = zlen;
+
+ vlen = strlen(val);
+ /* Characters preceding start index +
+ characters of what is assigned +
+ characters following end index */
+ newsize = v->start + vlen + (zlen - v->end);
+
+ /* Does new size differ? */
+ if (newsize != zlen || v->pm->gsu.s->setfn != strsetfn) {
+ x = (char *) zalloc(newsize + 1);
+ strncpy(x, z, v->start);
+ strcpy(x + v->start, val);
+ strcat(x + v->start, z + v->end);
+ v->pm->gsu.s->setfn(v->pm, x);
+ } else {
+ Param pm = v->pm;
+ /* Size doesn't change, can limit actions to only
+ * overwriting bytes in already allocated string */
+ strncpy(z + v->start, val, vlen);
+ /* Implement remainder of strsetfn */
+ if (!(pm->node.flags & PM_HASHELEM) &&
+ ((pm->node.flags & PM_NAMEDDIR) ||
+ isset(AUTONAMEDIRS))) {
+ pm->node.flags |= PM_NAMEDDIR;
+ adduserdir(pm->node.nam, z, 0, 0);
+ }
+ }
+ zsfree(val);
+ }
+ break;
+ case PM_INTEGER:
+ if (val) {
+ zlong ival;
+ if (flags & ASSPM_ENV_IMPORT) {
+ char *ptr;
+ ival = zstrtol_underscore(val, &ptr, 0, 1);
+ } else
+ ival = mathevali(val);
+ v->pm->gsu.i->setfn(v->pm, ival);
+ if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+ !v->pm->width)
+ v->pm->width = strlen(val);
+ zsfree(val);
+ }
+ if (!v->pm->base && lastbase != -1)
+ v->pm->base = lastbase;
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ if (val) {
+ mnumber mn;
+ if (flags & ASSPM_ENV_IMPORT) {
+ char *ptr;
+ mn.type = MN_FLOAT;
+ mn.u.d = strtod(val, &ptr);
+ } else
+ mn = matheval(val);
+ v->pm->gsu.f->setfn(v->pm, (mn.type & MN_FLOAT) ? mn.u.d :
+ (double)mn.u.l);
+ if ((v->pm->node.flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) &&
+ !v->pm->width)
+ v->pm->width = strlen(val);
+ zsfree(val);
+ }
+ break;
+ case PM_ARRAY:
+ {
+ char **ss = (char **) zalloc(2 * sizeof(char *));
+
+ ss[0] = val;
+ ss[1] = NULL;
+ setarrvalue(v, ss);
+ }
+ break;
+ case PM_HASHED:
+ {
+ if (foundparam == NULL)
+ {
+ zerr("%s: attempt to set associative array to scalar",
+ v->pm->node.nam);
+ zsfree(val);
+ return;
+ }
+ else
+ foundparam->gsu.s->setfn(foundparam, val);
+ }
+ break;
+ }
+ if ((!v->pm->env && !(v->pm->node.flags & PM_EXPORTED) &&
+ !(isset(ALLEXPORT) && !(v->pm->node.flags & PM_HASHELEM))) ||
+ (v->pm->node.flags & PM_ARRAY) || v->pm->ename)
+ return;
+ export_param(v->pm);
+}
+
+/**/
+void
+setnumvalue(Value v, mnumber val)
+{
+ char buf[BDIGBUFSIZE], *p;
+
+ if (unset(EXECOPT))
+ return;
+ if (v->pm->node.flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->node.nam);
+ return;
+ }
+ if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->node.nam);
+ return;
+ }
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_SCALAR:
+ case PM_ARRAY:
+ if ((val.type & MN_INTEGER) || outputradix) {
+ if (!(val.type & MN_INTEGER))
+ val.u.l = (zlong) val.u.d;
+ p = convbase_underscore(buf, val.u.l, outputradix,
+ outputunderscore);
+ } else
+ p = convfloat_underscore(val.u.d, outputunderscore);
+ setstrvalue(v, ztrdup(p));
+ break;
+ case PM_INTEGER:
+ v->pm->gsu.i->setfn(v->pm, (val.type & MN_INTEGER) ? val.u.l :
+ (zlong) val.u.d);
+ setstrvalue(v, NULL);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ v->pm->gsu.f->setfn(v->pm, (val.type & MN_INTEGER) ?
+ (double)val.u.l : val.u.d);
+ setstrvalue(v, NULL);
+ break;
+ }
+}
+
+/**/
+mod_export void
+setarrvalue(Value v, char **val)
+{
+ if (unset(EXECOPT))
+ return;
+ if (v->pm->node.flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->node.nam);
+ freearray(val);
+ return;
+ }
+ if ((v->pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", v->pm->node.nam);
+ freearray(val);
+ return;
+ }
+ if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED))) {
+ freearray(val);
+ zerr("%s: attempt to assign array value to non-array",
+ v->pm->node.nam);
+ return;
+ }
+ if (v->flags & VALFLAG_EMPTY) {
+ zerr("%s: assignment to invalid subscript range", v->pm->node.nam);
+ freearray(val);
+ return;
+ }
+
+ if (v->start == 0 && v->end == -1) {
+ if (PM_TYPE(v->pm->node.flags) == PM_HASHED)
+ arrhashsetfn(v->pm, val, 0);
+ else
+ v->pm->gsu.a->setfn(v->pm, val);
+ } else if (v->start == -1 && v->end == 0 &&
+ PM_TYPE(v->pm->node.flags) == PM_HASHED) {
+ arrhashsetfn(v->pm, val, ASSPM_AUGMENT);
+ } else if ((PM_TYPE(v->pm->node.flags) == PM_HASHED)) {
+ freearray(val);
+ zerr("%s: attempt to set slice of associative array",
+ v->pm->node.nam);
+ return;
+ } else {
+ char **const old = v->pm->gsu.a->getfn(v->pm);
+ char **new;
+ char **p, **q, **r; /* index variables */
+ const int pre_assignment_length = arrlen(old);
+ int post_assignment_length;
+ int i;
+
+ q = old;
+
+ if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS)) {
+ if (v->start > 0)
+ v->start--;
+ v->end--;
+ }
+ if (v->start < 0) {
+ v->start += pre_assignment_length;
+ if (v->start < 0)
+ v->start = 0;
+ }
+ if (v->end < 0) {
+ v->end += pre_assignment_length + 1;
+ if (v->end < 0)
+ v->end = 0;
+ }
+ if (v->end < v->start)
+ v->end = v->start;
+
+ post_assignment_length = v->start + arrlen(val);
+ if (v->end < pre_assignment_length) {
+ /*
+ * Allocate room for array elements between the end of the slice `v'
+ * and the original array's end.
+ */
+ post_assignment_length += pre_assignment_length - v->end;
+ }
+
+ if (pre_assignment_length == post_assignment_length
+ && v->pm->gsu.a->setfn == arrsetfn
+ /* ... and isn't something that arrsetfn() treats specially */
+ && 0 == (v->pm->node.flags & (PM_SPECIAL|PM_UNIQUE))
+ && NULL == v->pm->ename)
+ {
+ /* v->start is 0-based */
+ p = old + v->start;
+ for (r = val; *r;) {
+ /* Free previous string */
+ zsfree(*p);
+ /* Give away ownership of the string */
+ *p++ = *r++;
+ }
+ } else {
+ /* arr+=( ... )
+ * arr[${#arr}+x,...]=( ... ) */
+ if (post_assignment_length > pre_assignment_length &&
+ pre_assignment_length <= v->start &&
+ pre_assignment_length > 0 &&
+ v->pm->gsu.a->setfn == arrsetfn)
+ {
+ p = new = (char **) zrealloc(old, sizeof(char *)
+ * (post_assignment_length + 1));
+
+ p += pre_assignment_length; /* after old elements */
+
+ /* Consider 1 < 0, case for a=( 1 ); a[1,..] =
+ * 1 < 1, case for a=( 1 ); a[2,..] = */
+ if (pre_assignment_length < v->start) {
+ for (i = pre_assignment_length; i < v->start; i++) {
+ *p++ = ztrdup("");
+ }
+ }
+
+ for (r = val; *r;) {
+ /* Give away ownership of the string */
+ *p++ = *r++;
+ }
+
+ /* v->end doesn't matter:
+ * a=( 1 2 ); a[4,100]=( a b ); echo "${(q@)a}"
+ * 1 2 '' a b */
+ *p = NULL;
+
+ v->pm->u.arr = NULL;
+ v->pm->gsu.a->setfn(v->pm, new);
+ } else {
+ p = new = (char **) zalloc(sizeof(char *)
+ * (post_assignment_length + 1));
+ for (i = 0; i < v->start; i++)
+ *p++ = i < pre_assignment_length ? ztrdup(*q++) : ztrdup("");
+ for (r = val; *r;) {
+ /* Give away ownership of the string */
+ *p++ = *r++;
+ }
+ if (v->end < pre_assignment_length)
+ for (q = old + v->end; *q;)
+ *p++ = ztrdup(*q++);
+ *p = NULL;
+
+ v->pm->gsu.a->setfn(v->pm, new);
+ }
+
+ DPUTS2(p - new != post_assignment_length, "setarrvalue: wrong allocation: %d 1= %lu",
+ post_assignment_length, (unsigned long)(p - new));
+ }
+
+ /* Ownership of all strings has been
+ * given away, can plainly free */
+ free(val);
+ }
+}
+
+/* Retrieve an integer parameter */
+
+/**/
+mod_export zlong
+getiparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!(v = getvalue(&vbuf, &s, 1)))
+ return 0;
+ return getintvalue(v);
+}
+
+/* Retrieve a numerical parameter, either integer or floating */
+
+/**/
+mnumber
+getnparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!(v = getvalue(&vbuf, &s, 1))) {
+ mnumber mn;
+ mn.type = MN_INTEGER;
+ mn.u.l = 0;
+ return mn;
+ }
+ return getnumvalue(v);
+}
+
+/* Retrieve a scalar (string) parameter */
+
+/**/
+mod_export char *
+getsparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!(v = getvalue(&vbuf, &s, 0)))
+ return NULL;
+ return getstrvalue(v);
+}
+
+/**/
+mod_export char *
+getsparam_u(char *s)
+{
+ if ((s = getsparam(s)))
+ return unmetafy(s, NULL);
+ return s;
+}
+
+/* Retrieve an array parameter */
+
+/**/
+mod_export char **
+getaparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+ PM_TYPE(v->pm->node.flags) == PM_ARRAY)
+ return v->pm->gsu.a->getfn(v->pm);
+ return NULL;
+}
+
+/* Retrieve an assoc array parameter as an array */
+
+/**/
+mod_export char **
+gethparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+ PM_TYPE(v->pm->node.flags) == PM_HASHED)
+ return paramvalarr(v->pm->gsu.h->getfn(v->pm), SCANPM_WANTVALS);
+ return NULL;
+}
+
+/* Retrieve the keys of an assoc array parameter as an array */
+
+/**/
+mod_export char **
+gethkparam(char *s)
+{
+ struct value vbuf;
+ Value v;
+
+ if (!idigit(*s) && (v = getvalue(&vbuf, &s, 0)) &&
+ PM_TYPE(v->pm->node.flags) == PM_HASHED)
+ return paramvalarr(v->pm->gsu.h->getfn(v->pm), SCANPM_WANTKEYS);
+ return NULL;
+}
+
+/*
+ * Function behind WARNCREATEGLOBAL and WARNNESTEDVAR option.
+ *
+ * For WARNNESTEDVAR:
+ * Called when the variable is created.
+ * Apply heuristics to see if this variable was just created
+ * globally but in a local context.
+ *
+ * For WARNNESTEDVAR:
+ * Called when the variable already exists and is set.
+ * Apply heuristics to see if this variable is setting
+ * a variable that was created in a less nested function
+ * or globally.
+ */
+
+/**/
+static void
+check_warn_pm(Param pm, const char *pmtype, int created,
+ int may_warn_about_nested_vars)
+{
+ Funcstack i;
+
+ if (!may_warn_about_nested_vars && !created)
+ return;
+
+ if (created && isset(WARNCREATEGLOBAL)) {
+ if (locallevel <= forklevel || pm->level != 0)
+ return;
+ } else if (!created && isset(WARNNESTEDVAR)) {
+ if (pm->level >= locallevel)
+ return;
+ } else
+ return;
+
+ if (pm->node.flags & PM_SPECIAL)
+ return;
+
+ for (i = funcstack; i; i = i->prev) {
+ if (i->tp == FS_FUNC) {
+ char *msg;
+ DPUTS(!i->name, "funcstack entry with no name");
+ msg = created ?
+ "%s parameter %s created globally in function %s" :
+ "%s parameter %s set in enclosing scope in function %s";
+ zwarn(msg, pmtype, pm->node.nam, i->name);
+ break;
+ }
+ }
+}
+
+/**/
+mod_export Param
+assignsparam(char *s, char *val, int flags)
+{
+ struct value vbuf;
+ Value v;
+ char *t = s;
+ char *ss, *copy, *var;
+ size_t lvar;
+ mnumber lhs, rhs;
+ int sstart, created = 0;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s);
+ zsfree(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ queue_signals();
+ if ((ss = strchr(s, '['))) {
+ *ss = '\0';
+ if (!(v = getvalue(&vbuf, &s, 1))) {
+ createparam(t, PM_ARRAY);
+ created = 1;
+ } else {
+ if (v->pm->node.flags & PM_READONLY) {
+ zerr("read-only variable: %s", v->pm->node.nam);
+ *ss = '[';
+ zsfree(val);
+ unqueue_signals();
+ return NULL;
+ }
+ /*
+ * Parameter defined here is a temporary bogus one.
+ * Don't warn about anything.
+ */
+ flags &= ~ASSPM_WARN;
+ }
+ *ss = '[';
+ v = NULL;
+ } else {
+ if (!(v = getvalue(&vbuf, &s, 1))) {
+ createparam(t, PM_SCALAR);
+ created = 1;
+ } else if ((((v->pm->node.flags & PM_ARRAY) && !(flags & ASSPM_AUGMENT)) ||
+ (v->pm->node.flags & PM_HASHED)) &&
+ !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) &&
+ unset(KSHARRAYS)) {
+ unsetparam(t);
+ createparam(t, PM_SCALAR);
+ /* not regarded as a new creation */
+ v = NULL;
+ }
+ }
+ if (!v && !(v = getvalue(&vbuf, &t, 1))) {
+ unqueue_signals();
+ zsfree(val);
+ /* errflag |= ERRFLAG_ERROR; */
+ return NULL;
+ }
+ if (flags & ASSPM_WARN)
+ check_warn_pm(v->pm, "scalar", created, 1);
+ if (flags & ASSPM_AUGMENT) {
+ if (v->start == 0 && v->end == -1) {
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_SCALAR:
+ v->start = INT_MAX; /* just append to scalar value */
+ break;
+ case PM_INTEGER:
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ rhs = matheval(val);
+ lhs = getnumvalue(v);
+ if (lhs.type == MN_FLOAT) {
+ if ((rhs.type) == MN_FLOAT)
+ lhs.u.d = lhs.u.d + rhs.u.d;
+ else
+ lhs.u.d = lhs.u.d + (double)rhs.u.l;
+ } else {
+ if ((rhs.type) == MN_INTEGER)
+ lhs.u.l = lhs.u.l + rhs.u.l;
+ else
+ lhs.u.l = lhs.u.l + (zlong)rhs.u.d;
+ }
+ setnumvalue(v, lhs);
+ unqueue_signals();
+ zsfree(val);
+ return v->pm; /* avoid later setstrvalue() call */
+ case PM_ARRAY:
+ if (unset(KSHARRAYS)) {
+ v->start = arrlen(v->pm->gsu.a->getfn(v->pm));
+ v->end = v->start + 1;
+ } else {
+ /* ksh appends scalar to first element */
+ v->end = 1;
+ goto kshappend;
+ }
+ break;
+ }
+ } else {
+ switch (PM_TYPE(v->pm->node.flags)) {
+ case PM_SCALAR:
+ if (v->end > 0)
+ v->start = v->end;
+ else
+ v->start = v->end = strlen(v->pm->gsu.s->getfn(v->pm)) +
+ v->end + 1;
+ break;
+ case PM_INTEGER:
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ unqueue_signals();
+ zerr("attempt to add to slice of a numeric variable");
+ zsfree(val);
+ return NULL;
+ case PM_ARRAY:
+ kshappend:
+ /* treat slice as the end element */
+ v->start = sstart = v->end > 0 ? v->end - 1 : v->end;
+ v->isarr = 0;
+ var = getstrvalue(v);
+ v->start = sstart;
+ copy = val;
+ lvar = strlen(var);
+ val = (char *)zalloc(lvar + strlen(val) + 1);
+ strcpy(val, var);
+ strcpy(val + lvar, copy);
+ zsfree(copy);
+ break;
+ }
+ }
+ }
+
+ assignstrvalue(v, val, flags);
+ unqueue_signals();
+ return v->pm;
+}
+
+/**/
+mod_export Param
+setsparam(char *s, char *val)
+{
+ return assignsparam(s, val, ASSPM_WARN);
+}
+
+/**/
+mod_export Param
+assignaparam(char *s, char **val, int flags)
+{
+ struct value vbuf;
+ Value v;
+ char *t = s;
+ char *ss;
+ int created = 0;
+ int may_warn_about_nested_vars = 1;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s);
+ freearray(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ queue_signals();
+ if ((ss = strchr(s, '['))) {
+ *ss = '\0';
+ if (!(v = getvalue(&vbuf, &s, 1))) {
+ createparam(t, PM_ARRAY);
+ created = 1;
+ } else {
+ may_warn_about_nested_vars = 0;
+ }
+ *ss = '[';
+ if (v && PM_TYPE(v->pm->node.flags) == PM_HASHED) {
+ unqueue_signals();
+ zerr("%s: attempt to set slice of associative array",
+ v->pm->node.nam);
+ freearray(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ v = NULL;
+ } else {
+ if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) {
+ createparam(t, PM_ARRAY);
+ created = 1;
+ } else if (!(PM_TYPE(v->pm->node.flags) & (PM_ARRAY|PM_HASHED)) &&
+ !(v->pm->node.flags & (PM_SPECIAL|PM_TIED))) {
+ int uniq = v->pm->node.flags & PM_UNIQUE;
+ if (flags & ASSPM_AUGMENT) {
+ /* insert old value at the beginning of the val array */
+ char **new;
+ int lv = arrlen(val);
+
+ new = (char **) zalloc(sizeof(char *) * (lv + 2));
+ *new = ztrdup(getstrvalue(v));
+ memcpy(new+1, val, sizeof(char *) * (lv + 1));
+ free(val);
+ val = new;
+ }
+ unsetparam(t);
+ createparam(t, PM_ARRAY | uniq);
+ v = NULL;
+ }
+ }
+ if (!v)
+ if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) {
+ unqueue_signals();
+ freearray(val);
+ /* errflag |= ERRFLAG_ERROR; */
+ return NULL;
+ }
+
+ if (flags & ASSPM_WARN)
+ check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars);
+
+ /*
+ * At this point, we may have array entries consisting of
+ * - a Marker element --- normally allocated array entry but
+ * with just Marker char and null
+ * - an array index element --- as normal for associative array,
+ * but non-standard for normal array which we handle now.
+ * - a value for the indexed element.
+ * This only applies if the flag ASSPM_KEY_VALUE is passed in,
+ * indicating prefork() detected this syntax.
+ *
+ * For associative arrays we just junk the Marker elements.
+ */
+ if (flags & ASSPM_KEY_VALUE) {
+ char **aptr;
+ if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
+ /*
+ * This is an ordinary array with key / value pairs.
+ */
+ int maxlen, origlen, nextind;
+ char **fullval, **origptr;
+ zlong *subscripts = (zlong *)zhalloc(arrlen(val) * sizeof(zlong));
+ zlong *iptr = subscripts;
+ if (flags & ASSPM_AUGMENT) {
+ origptr = v->pm->gsu.a->getfn(v->pm);
+ maxlen = origlen = arrlen(origptr);
+ } else {
+ maxlen = origlen = 0;
+ origptr = NULL;
+ }
+ nextind = 0;
+ for (aptr = val; *aptr; ) {
+ if (**aptr == Marker) {
+ *iptr = mathevali(*++aptr);
+ if (*iptr < 0 ||
+ (!isset(KSHARRAYS) && *iptr == 0)) {
+ unqueue_signals();
+ zerr("bad subscript for direct array assignment: %s", *aptr);
+ freearray(val);
+ return NULL;
+ }
+ if (!isset(KSHARRAYS))
+ --*iptr;
+ nextind = *iptr + 1;
+ ++iptr;
+ aptr += 2;
+ } else {
+ ++nextind;
+ ++aptr;
+ }
+ if (nextind > maxlen)
+ maxlen = nextind;
+ }
+ fullval = zshcalloc((maxlen+1) * sizeof(char *));
+ if (!fullval) {
+ zerr("array too large");
+ freearray(val);
+ return NULL;
+ }
+ fullval[maxlen] = NULL;
+ if (flags & ASSPM_AUGMENT) {
+ char **srcptr = origptr;
+ for (aptr = fullval; aptr <= fullval + origlen; aptr++) {
+ *aptr = ztrdup(*srcptr);
+ srcptr++;
+ }
+ }
+ iptr = subscripts;
+ nextind = 0;
+ for (aptr = val; *aptr; ++aptr) {
+ char *old;
+ if (**aptr == Marker) {
+ int augment = ((*aptr)[1] == '+');
+ zsfree(*aptr);
+ zsfree(*++aptr); /* Index, no longer needed */
+ old = fullval[*iptr];
+ if (augment && old) {
+ fullval[*iptr] = bicat(old, *++aptr);
+ zsfree(*aptr);
+ } else {
+ fullval[*iptr] = *++aptr;
+ }
+ nextind = *iptr + 1;
+ ++iptr;
+ } else {
+ old = fullval[nextind];
+ fullval[nextind] = *aptr;
+ ++nextind;
+ }
+ if (old)
+ zsfree(old);
+ /* aptr now on value in both cases */
+ }
+ if (*aptr) { /* Shouldn't be possible */
+ DPUTS(1, "Extra element in key / value array");
+ zsfree(*aptr);
+ }
+ free(val);
+ for (aptr = fullval; aptr < fullval + maxlen; aptr++) {
+ /*
+ * Remember we don't have sparse arrays but and they're null
+ * terminated --- so any value we don't set has to be an
+ * empty string.
+ */
+ if (!*aptr)
+ *aptr = ztrdup("");
+ }
+ setarrvalue(v, fullval);
+ unqueue_signals();
+ return v->pm;
+ } else if (PM_TYPE(v->pm->node.flags & PM_HASHED)) {
+ /*
+ * We strictly enforce [key]=value syntax for associative
+ * arrays. Marker can only indicate a Marker / key / value
+ * triad; it cannot be there by accident.
+ *
+ * It's too inefficient to strip Markers here, and they
+ * can't be there in the other form --- so just ignore
+ * them willy nilly lower down.
+ */
+ for (aptr = val; *aptr; aptr += 3) {
+ if (**aptr != Marker) {
+ unqueue_signals();
+ freearray(val);
+ zerr("bad [key]=value syntax for associative array");
+ return NULL;
+ }
+ }
+ } else {
+ unqueue_signals();
+ freearray(val);
+ zerr("invalid use of [key]=value assignment syntax");
+ return NULL;
+ }
+ }
+
+ if (flags & ASSPM_AUGMENT) {
+ if (v->start == 0 && v->end == -1) {
+ if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
+ v->start = arrlen(v->pm->gsu.a->getfn(v->pm));
+ v->end = v->start + 1;
+ } else if (PM_TYPE(v->pm->node.flags) & PM_HASHED)
+ v->start = -1, v->end = 0;
+ } else {
+ if (v->end > 0)
+ v->start = v->end--;
+ else if (PM_TYPE(v->pm->node.flags) & PM_ARRAY) {
+ v->end = arrlen(v->pm->gsu.a->getfn(v->pm)) + v->end;
+ v->start = v->end + 1;
+ }
+ }
+ }
+
+ setarrvalue(v, val);
+ unqueue_signals();
+ return v->pm;
+}
+
+
+/**/
+mod_export Param
+setaparam(char *s, char **aval)
+{
+ return assignaparam(s, aval, ASSPM_WARN);
+}
+
+/**/
+mod_export Param
+sethparam(char *s, char **val)
+{
+ struct value vbuf;
+ Value v;
+ char *t = s;
+ int checkcreate = 0;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s);
+ freearray(val);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ if (strchr(s, '[')) {
+ freearray(val);
+ zerr("nested associative arrays not yet supported");
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ if (unset(EXECOPT))
+ return NULL;
+ queue_signals();
+ if (!(v = fetchvalue(&vbuf, &s, 1, SCANPM_ASSIGNING))) {
+ createparam(t, PM_HASHED);
+ checkcreate = 1;
+ } else if (!(PM_TYPE(v->pm->node.flags) & PM_HASHED)) {
+ if (!(v->pm->node.flags & PM_SPECIAL)) {
+ unsetparam(t);
+ /* no WARNCREATEGLOBAL check here as parameter already existed */
+ createparam(t, PM_HASHED);
+ v = NULL;
+ } else {
+ zerr("%s: can't change type of a special parameter", t);
+ unqueue_signals();
+ return NULL;
+ }
+ }
+ if (!v)
+ if (!(v = fetchvalue(&vbuf, &t, 1, SCANPM_ASSIGNING))) {
+ unqueue_signals();
+ /* errflag |= ERRFLAG_ERROR; */
+ return NULL;
+ }
+ check_warn_pm(v->pm, "associative array", checkcreate, 1);
+ setarrvalue(v, val);
+ unqueue_signals();
+ return v->pm;
+}
+
+
+/*
+ * Set a generic shell number, floating point or integer.
+ * Option to warn on setting.
+ */
+
+/**/
+mod_export Param
+assignnparam(char *s, mnumber val, int flags)
+{
+ struct value vbuf;
+ Value v;
+ char *t = s, *ss;
+ Param pm;
+ int was_unset = 0;
+
+ if (!isident(s)) {
+ zerr("not an identifier: %s", s);
+ errflag |= ERRFLAG_ERROR;
+ return NULL;
+ }
+ if (unset(EXECOPT))
+ return NULL;
+ queue_signals();
+ ss = strchr(s, '[');
+ v = getvalue(&vbuf, &s, 1);
+ if (v && (v->pm->node.flags & (PM_ARRAY|PM_HASHED)) &&
+ !(v->pm->node.flags & (PM_SPECIAL|PM_TIED)) &&
+ /*
+ * not sure what KSHARRAYS has got to do with this...
+ * copied this from assignsparam().
+ */
+ unset(KSHARRAYS) && !ss) {
+ unsetparam_pm(v->pm, 0, 1);
+ was_unset = 1;
+ s = t;
+ v = NULL;
+ }
+ if (!v) {
+ /* s has been updated by getvalue, so check again */
+ ss = strchr(s, '[');
+ if (ss)
+ *ss = '\0';
+ pm = createparam(t, ss ? PM_ARRAY :
+ isset(POSIXIDENTIFIERS) ? PM_SCALAR :
+ (val.type & MN_INTEGER) ? PM_INTEGER : PM_FFLOAT);
+ if (!pm)
+ pm = (Param) paramtab->getnode(paramtab, t);
+ DPUTS(!pm, "BUG: parameter not created");
+ if (ss) {
+ *ss = '[';
+ } else if (val.type & MN_INTEGER) {
+ pm->base = outputradix;
+ }
+ if (!(v = getvalue(&vbuf, &t, 1))) {
+ DPUTS(!v, "BUG: value not found for new parameter");
+ /* errflag |= ERRFLAG_ERROR; */
+ unqueue_signals();
+ return NULL;
+ }
+ if (flags & ASSPM_WARN)
+ check_warn_pm(v->pm, "numeric", !was_unset, 1);
+ } else {
+ if (flags & ASSPM_WARN)
+ check_warn_pm(v->pm, "numeric", 0, 1);
+ }
+ setnumvalue(v, val);
+ unqueue_signals();
+ return v->pm;
+}
+
+/*
+ * Set a generic shell number, floating point or integer.
+ * Warn on setting based on option.
+ */
+
+/**/
+mod_export Param
+setnparam(char *s, mnumber val)
+{
+ return assignnparam(s, val, ASSPM_WARN);
+}
+
+/* Simplified interface to assignnparam */
+
+/**/
+mod_export Param
+assigniparam(char *s, zlong val, int flags)
+{
+ mnumber mnval;
+ mnval.type = MN_INTEGER;
+ mnval.u.l = val;
+ return assignnparam(s, mnval, flags);
+}
+
+/* Simplified interface to setnparam */
+
+/**/
+mod_export Param
+setiparam(char *s, zlong val)
+{
+ mnumber mnval;
+ mnval.type = MN_INTEGER;
+ mnval.u.l = val;
+ return assignnparam(s, mnval, ASSPM_WARN);
+}
+
+/*
+ * Set an integer parameter without forcing creation of an integer type.
+ * This is useful if the integer is going to be set to a parmaeter which
+ * would usually be scalar but may not exist.
+ */
+
+/**/
+mod_export Param
+setiparam_no_convert(char *s, zlong val)
+{
+ /*
+ * If the target is already an integer, thisgets converted
+ * back. Low technology rules.
+ */
+ char buf[BDIGBUFSIZE];
+ convbase(buf, val, 10);
+ return assignsparam(s, ztrdup(buf), ASSPM_WARN);
+}
+
+/* Unset a parameter */
+
+/**/
+mod_export void
+unsetparam(char *s)
+{
+ Param pm;
+
+ queue_signals();
+ if ((pm = (Param) (paramtab == realparamtab ?
+ /* getnode2() to avoid autoloading */
+ paramtab->getnode2(paramtab, s) :
+ paramtab->getnode(paramtab, s))))
+ unsetparam_pm(pm, 0, 1);
+ unqueue_signals();
+}
+
+/* Unset a parameter
+ *
+ * altflag: if true, don't remove pm->ename from the environment
+ * exp: See stdunsetfn()
+ */
+
+/**/
+mod_export int
+unsetparam_pm(Param pm, int altflag, int exp)
+{
+ Param oldpm, altpm;
+ char *altremove;
+
+ if ((pm->node.flags & PM_READONLY) && pm->level <= locallevel) {
+ zerr("read-only variable: %s", pm->node.nam);
+ return 1;
+ }
+ if ((pm->node.flags & PM_RESTRICTED) && isset(RESTRICTED)) {
+ zerr("%s: restricted", pm->node.nam);
+ return 1;
+ }
+
+ if (pm->ename && !altflag)
+ altremove = ztrdup(pm->ename);
+ else
+ altremove = NULL;
+
+ if (!(pm->node.flags & PM_UNSET))
+ pm->gsu.s->unsetfn(pm, exp);
+ if (pm->env)
+ delenv(pm);
+
+ /* remove it under its alternate name if necessary */
+ if (altremove) {
+ altpm = (Param) paramtab->getnode(paramtab, altremove);
+ /* tied parameters are at the same local level as each other */
+ oldpm = NULL;
+ while (altpm && altpm->level > pm->level) {
+ /* param under alternate name hidden by a local */
+ oldpm = altpm;
+ altpm = altpm->old;
+ }
+ if (altpm) {
+ if (oldpm && !altpm->level) {
+ oldpm->old = NULL;
+ /* fudge things so removenode isn't called */
+ altpm->level = 1;
+ }
+ unsetparam_pm(altpm, 1, exp);
+ }
+
+ zsfree(altremove);
+ }
+
+ /*
+ * If this was a local variable, we need to keep the old
+ * struct so that it is resurrected at the right level.
+ * This is partly because when an array/scalar value is set
+ * and the parameter used to be the other sort, unsetparam()
+ * is called. Beyond that, there is an ambiguity: should
+ * foo() { local bar; unset bar; } make the global bar
+ * available or not? The following makes the answer "no".
+ *
+ * Some specials, such as those used in zle, still need removing
+ * from the parameter table; they have the PM_REMOVABLE flag.
+ */
+ if ((pm->level && locallevel >= pm->level) ||
+ (pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL)
+ return 0;
+
+ /* remove parameter node from table */
+ paramtab->removenode(paramtab, pm->node.nam);
+
+ if (pm->old) {
+ oldpm = pm->old;
+ paramtab->addnode(paramtab, oldpm->node.nam, oldpm);
+ if ((PM_TYPE(oldpm->node.flags) == PM_SCALAR) &&
+ !(pm->node.flags & PM_HASHELEM) &&
+ (oldpm->node.flags & PM_NAMEDDIR) &&
+ oldpm->gsu.s == &stdscalar_gsu)
+ adduserdir(oldpm->node.nam, oldpm->u.str, 0, 0);
+ if (oldpm->node.flags & PM_EXPORTED) {
+ /*
+ * Re-export the old value which we removed in typeset_single().
+ * I don't think we need to test for ALL_EXPORT here, since if
+ * it was used to export the parameter originally the parameter
+ * should still have the PM_EXPORTED flag.
+ */
+ export_param(oldpm);
+ }
+ }
+
+ paramtab->freenode(&pm->node); /* free parameter node */
+
+ return 0;
+}
+
+/* Standard function to unset a parameter. This is mostly delegated to *
+ * the specific set function.
+ *
+ * This could usefully be made type-specific, but then we need
+ * to be more careful when calling the unset method directly.
+ *
+ * The "exp"licit parameter should be nonzero for assignments and the
+ * unset command, and zero for implicit unset (e.g., end of scope).
+ * Currently this is used only by some modules.
+ */
+
+/**/
+mod_export void
+stdunsetfn(Param pm, UNUSED(int exp))
+{
+ switch (PM_TYPE(pm->node.flags)) {
+ case PM_SCALAR:
+ if (pm->gsu.s->setfn)
+ pm->gsu.s->setfn(pm, NULL);
+ break;
+
+ case PM_ARRAY:
+ if (pm->gsu.a->setfn)
+ pm->gsu.a->setfn(pm, NULL);
+ break;
+
+ case PM_HASHED:
+ if (pm->gsu.h->setfn)
+ pm->gsu.h->setfn(pm, NULL);
+ break;
+
+ default:
+ if (!(pm->node.flags & PM_SPECIAL))
+ pm->u.str = NULL;
+ break;
+ }
+ if ((pm->node.flags & (PM_SPECIAL|PM_TIED)) == PM_TIED) {
+ if (pm->ename) {
+ zsfree(pm->ename);
+ pm->ename = NULL;
+ }
+ pm->node.flags &= ~PM_TIED;
+ }
+ pm->node.flags |= PM_UNSET;
+}
+
+/* Function to get value of an integer parameter */
+
+/**/
+mod_export zlong
+intgetfn(Param pm)
+{
+ return pm->u.val;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+intsetfn(Param pm, zlong x)
+{
+ pm->u.val = x;
+}
+
+/* Function to get value of a floating point parameter */
+
+/**/
+static double
+floatgetfn(Param pm)
+{
+ return pm->u.dval;
+}
+
+/* Function to set value of an integer parameter */
+
+/**/
+static void
+floatsetfn(Param pm, double x)
+{
+ pm->u.dval = x;
+}
+
+/* Function to get value of a scalar (string) parameter */
+
+/**/
+mod_export char *
+strgetfn(Param pm)
+{
+ return pm->u.str ? pm->u.str : (char *) hcalloc(1);
+}
+
+/* Function to set value of a scalar (string) parameter */
+
+/**/
+mod_export void
+strsetfn(Param pm, char *x)
+{
+ zsfree(pm->u.str);
+ pm->u.str = x;
+ if (!(pm->node.flags & PM_HASHELEM) &&
+ ((pm->node.flags & PM_NAMEDDIR) || isset(AUTONAMEDIRS))) {
+ pm->node.flags |= PM_NAMEDDIR;
+ adduserdir(pm->node.nam, x, 0, 0);
+ }
+ /* If you update this function, you may need to update the
+ * `Implement remainder of strsetfn' block in assignstrvalue(). */
+}
+
+/* Function to get value of an array parameter */
+
+static char *nullarray = NULL;
+
+/**/
+char **
+arrgetfn(Param pm)
+{
+ return pm->u.arr ? pm->u.arr : &nullarray;
+}
+
+/* Function to set value of an array parameter */
+
+/**/
+mod_export void
+arrsetfn(Param pm, char **x)
+{
+ if (pm->u.arr && pm->u.arr != x)
+ freearray(pm->u.arr);
+ if (pm->node.flags & PM_UNIQUE)
+ uniqarray(x);
+ pm->u.arr = x;
+ /* Arrays tied to colon-arrays may need to fix the environment */
+ if (pm->ename && x)
+ arrfixenv(pm->ename, x);
+ /* If you extend this function, update the list of conditions in
+ * setarrvalue(). */
+}
+
+/* Function to get value of an association parameter */
+
+/**/
+mod_export HashTable
+hashgetfn(Param pm)
+{
+ return pm->u.hash;
+}
+
+/* Function to set value of an association parameter */
+
+/**/
+mod_export void
+hashsetfn(Param pm, HashTable x)
+{
+ if (pm->u.hash && pm->u.hash != x)
+ deleteparamtable(pm->u.hash);
+ pm->u.hash = x;
+}
+
+/* Function to dispose of setting of an unsettable hash */
+
+/**/
+mod_export void
+nullsethashfn(UNUSED(Param pm), HashTable x)
+{
+ deleteparamtable(x);
+}
+
+/* Function to set value of an association parameter using key/value pairs */
+
+/**/
+static void
+arrhashsetfn(Param pm, char **val, int flags)
+{
+ /* Best not to shortcut this by using the existing hash table, *
+ * since that could cause trouble for special hashes. This way, *
+ * it's up to pm->gsu.h->setfn() what to do. */
+ int alen = 0;
+ HashTable opmtab = paramtab, ht = 0;
+ char **aptr;
+ Value v = (Value) hcalloc(sizeof *v);
+ v->end = -1;
+
+ for (aptr = val; *aptr; ++aptr) {
+ if (**aptr != Marker)
+ ++alen;
+ }
+
+ if (alen % 2) {
+ freearray(val);
+ zerr("bad set of key/value pairs for associative array");
+ return;
+ }
+ if (flags & ASSPM_AUGMENT) {
+ ht = paramtab = pm->gsu.h->getfn(pm);
+ }
+ if (alen && (!(flags & ASSPM_AUGMENT) || !paramtab)) {
+ ht = paramtab = newparamtable(17, pm->node.nam);
+ }
+ for (aptr = val; *aptr; ) {
+ int eltflags = 0;
+ if (**aptr == Marker) {
+ /* Either all elements have Marker or none. Checked in caller. */
+ if ((*aptr)[1] == '+') {
+ /* Actually, assignstrvalue currently doesn't handle this... */
+ eltflags = ASSPM_AUGMENT;
+ /* ...so we'll use the trick from setsparam(). */
+ v->start = INT_MAX;
+ } else {
+ v->start = 0;
+ }
+ v->end = -1;
+ zsfree(*aptr++);
+ }
+ /* The parameter name is ztrdup'd... */
+ v->pm = createparam(*aptr, PM_SCALAR|PM_UNSET);
+ /*
+ * createparam() doesn't return anything if the parameter
+ * already existed.
+ */
+ if (!v->pm)
+ v->pm = (Param) paramtab->getnode(paramtab, *aptr);
+ zsfree(*aptr++);
+ /* ...but we can use the value without copying. */
+ assignstrvalue(v, *aptr++, eltflags);
+ }
+ paramtab = opmtab;
+ pm->gsu.h->setfn(pm, ht);
+ free(val); /* not freearray() */
+}
+
+/*
+ * These functions are used as the set function for special parameters that
+ * cannot be set by the user. The set is incomplete as the only such
+ * parameters are scalar and integer.
+ */
+
+/**/
+mod_export void
+nullstrsetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(x);
+}
+
+/**/
+mod_export void
+nullintsetfn(UNUSED(Param pm), UNUSED(zlong x))
+{}
+
+/**/
+mod_export void
+nullunsetfn(UNUSED(Param pm), UNUSED(int exp))
+{}
+
+
+/* Function to get value of generic special integer *
+ * parameter. data is pointer to global variable *
+ * containing the integer value. */
+
+/**/
+mod_export zlong
+intvargetfn(Param pm)
+{
+ return *pm->u.valptr;
+}
+
+/* Function to set value of generic special integer *
+ * parameter. data is pointer to global variable *
+ * where the value is to be stored. */
+
+/**/
+mod_export void
+intvarsetfn(Param pm, zlong x)
+{
+ *pm->u.valptr = x;
+}
+
+/* Function to set value of any ZLE-related integer *
+ * parameter. data is pointer to global variable *
+ * where the value is to be stored. */
+
+/**/
+void
+zlevarsetfn(Param pm, zlong x)
+{
+ zlong *p = pm->u.valptr;
+
+ *p = x;
+ if (p == &zterm_lines || p == &zterm_columns)
+ adjustwinsize(2 + (p == &zterm_columns));
+}
+
+
+/* Implements gsu_integer.unsetfn for ZLE_RPROMPT_INDENT; see stdunsetfn() */
+
+static void
+rprompt_indent_unsetfn(Param pm, int exp)
+{
+ stdunsetfn(pm, exp);
+ rprompt_indent = 1; /* Keep this in sync with init_term() */
+}
+
+/* Function to set value of generic special scalar *
+ * parameter. data is pointer to a character pointer *
+ * representing the scalar (string). */
+
+/**/
+mod_export void
+strvarsetfn(Param pm, char *x)
+{
+ char **q = ((char **)pm->u.data);
+
+ zsfree(*q);
+ *q = x;
+}
+
+/* Function to get value of generic special scalar *
+ * parameter. data is pointer to a character pointer *
+ * representing the scalar (string). */
+
+/**/
+mod_export char *
+strvargetfn(Param pm)
+{
+ char *s = *((char **)pm->u.data);
+
+ if (!s)
+ return hcalloc(1);
+ return s;
+}
+
+/* Function to get value of generic special array *
+ * parameter. data is a pointer to the pointer to *
+ * a pointer (a pointer to a variable length array *
+ * of pointers). */
+
+/**/
+mod_export char **
+arrvargetfn(Param pm)
+{
+ char **arrptr = *((char ***)pm->u.data);
+
+ return arrptr ? arrptr : &nullarray;
+}
+
+/* Function to set value of generic special array parameter. *
+ * data is pointer to a variable length array of pointers which *
+ * represents this array of scalars (strings). If pm->ename is *
+ * non NULL, then it is a colon separated environment variable *
+ * version of this array which will need to be updated. */
+
+/**/
+mod_export void
+arrvarsetfn(Param pm, char **x)
+{
+ char ***dptr = (char ***)pm->u.data;
+
+ if (*dptr != x)
+ freearray(*dptr);
+ if (pm->node.flags & PM_UNIQUE)
+ uniqarray(x);
+ /*
+ * Special tied arrays point to variables accessible in other
+ * ways which need to be set to NULL. We can't do this
+ * with user tied variables since we can leak memory.
+ */
+ if ((pm->node.flags & PM_SPECIAL) && !x)
+ *dptr = mkarray(NULL);
+ else
+ *dptr = x;
+ if (pm->ename) {
+ if (x)
+ arrfixenv(pm->ename, x);
+ else if (*dptr == path)
+ pathchecked = path;
+ }
+}
+
+/**/
+char *
+colonarrgetfn(Param pm)
+{
+ char ***dptr = (char ***)pm->u.data;
+ return *dptr ? zjoin(*dptr, ':', 1) : "";
+}
+
+/**/
+void
+colonarrsetfn(Param pm, char *x)
+{
+ char ***dptr = (char ***)pm->u.data;
+ /*
+ * We have to make sure this is never NULL, since that
+ * can cause problems.
+ */
+ if (*dptr)
+ freearray(*dptr);
+ if (x)
+ *dptr = colonsplit(x, pm->node.flags & PM_UNIQUE);
+ else
+ *dptr = mkarray(NULL);
+ arrfixenv(pm->node.nam, *dptr);
+ zsfree(x);
+}
+
+/**/
+char *
+tiedarrgetfn(Param pm)
+{
+ struct tieddata *dptr = (struct tieddata *)pm->u.data;
+ return *dptr->arrptr ? zjoin(*dptr->arrptr, STOUC(dptr->joinchar), 1) : "";
+}
+
+/**/
+void
+tiedarrsetfn(Param pm, char *x)
+{
+ struct tieddata *dptr = (struct tieddata *)pm->u.data;
+
+ if (*dptr->arrptr)
+ freearray(*dptr->arrptr);
+ if (x) {
+ char sepbuf[3];
+ if (imeta(dptr->joinchar))
+ {
+ sepbuf[0] = Meta;
+ sepbuf[1] = dptr->joinchar ^ 32;
+ sepbuf[2] = '\0';
+ }
+ else
+ {
+ sepbuf[0] = dptr->joinchar;
+ sepbuf[1] = '\0';
+ }
+ *dptr->arrptr = sepsplit(x, sepbuf, 0, 0);
+ if (pm->node.flags & PM_UNIQUE)
+ uniqarray(*dptr->arrptr);
+ zsfree(x);
+ } else
+ *dptr->arrptr = NULL;
+ if (pm->ename)
+ arrfixenv(pm->node.nam, *dptr->arrptr);
+}
+
+/**/
+void
+tiedarrunsetfn(Param pm, UNUSED(int exp))
+{
+ /*
+ * Special unset function because we allocated a struct tieddata
+ * in typeset_single to hold the special data which we now
+ * need to delete.
+ */
+ pm->gsu.s->setfn(pm, NULL);
+ zfree(pm->u.data, sizeof(struct tieddata));
+ /* paranoia -- shouldn't need these, but in case we reuse the struct... */
+ pm->u.data = NULL;
+ zsfree(pm->ename);
+ pm->ename = NULL;
+ pm->node.flags &= ~PM_TIED;
+ pm->node.flags |= PM_UNSET;
+}
+
+/**/
+static void
+simple_arrayuniq(char **x, int freeok)
+{
+ char **t, **p = x;
+ char *hole = "";
+
+ /* Find duplicates and replace them with holes */
+ while (*++p)
+ for (t = x; t < p; t++)
+ if (*t != hole && !strcmp(*p, *t)) {
+ if (freeok)
+ zsfree(*p);
+ *p = hole;
+ break;
+ }
+ /* Swap non-holes into holes in optimal jumps */
+ for (p = t = x; *t != NULL; t++) {
+ if (*t == hole) {
+ while (*p == hole)
+ ++p;
+ if ((*t = *p) != NULL)
+ *p++ = hole;
+ } else if (p == t)
+ p++;
+ }
+ /* Erase all the remaining holes, just in case */
+ while (++t < p)
+ *t = NULL;
+}
+
+/**/
+static void
+arrayuniq_freenode(HashNode hn)
+{
+ (void)hn;
+}
+
+/**/
+HashTable
+newuniqtable(zlong size)
+{
+ HashTable ht = newhashtable((int)size, "arrayuniq", NULL);
+ /* ??? error checking */
+
+ ht->hash = hasher;
+ ht->emptytable = emptyhashtable;
+ ht->filltable = NULL;
+ ht->cmpnodes = strcmp;
+ ht->addnode = addhashnode;
+ ht->getnode = gethashnode2;
+ ht->getnode2 = gethashnode2;
+ ht->removenode = removehashnode;
+ ht->disablenode = disablehashnode;
+ ht->enablenode = enablehashnode;
+ ht->freenode = arrayuniq_freenode;
+ ht->printnode = NULL;
+
+ return ht;
+}
+
+/**/
+static void
+arrayuniq(char **x, int freeok)
+{
+ char **it, **write_it;
+ zlong array_size = arrlen(x);
+ HashTable ht;
+
+ if (array_size == 0)
+ return;
+ if (array_size < 10 || !(ht = newuniqtable(array_size + 1))) {
+ /* fallback to simpler routine */
+ simple_arrayuniq(x, freeok);
+ return;
+ }
+
+ for (it = x, write_it = x; *it;) {
+ if (! gethashnode2(ht, *it)) {
+ HashNode new_node = zhalloc(sizeof(struct hashnode));
+ if (!new_node) {
+ /* Oops, out of heap memory, no way to recover */
+ zerr("out of memory in arrayuniq");
+ break;
+ }
+ (void) addhashnode2(ht, *it, new_node);
+ *write_it = *it;
+ if (it != write_it)
+ *it = NULL;
+ ++write_it;
+ }
+ else {
+ if (freeok)
+ zsfree(*it);
+ *it = NULL;
+ }
+ ++it;
+ }
+
+ deletehashtable(ht);
+}
+
+/**/
+void
+uniqarray(char **x)
+{
+ if (!x || !*x)
+ return;
+ arrayuniq(x, !zheapptr(*x));
+}
+
+/**/
+void
+zhuniqarray(char **x)
+{
+ if (!x || !*x)
+ return;
+ arrayuniq(x, 0);
+}
+
+/* Function to get value of special parameter `#' and `ARGC' */
+
+/**/
+zlong
+poundgetfn(UNUSED(Param pm))
+{
+ return arrlen(pparams);
+}
+
+/* Function to get value for special parameter `RANDOM' */
+
+/**/
+zlong
+randomgetfn(UNUSED(Param pm))
+{
+ return rand() & 0x7fff;
+}
+
+/* Function to set value of special parameter `RANDOM' */
+
+/**/
+void
+randomsetfn(UNUSED(Param pm), zlong v)
+{
+ srand((unsigned int)v);
+}
+
+/* Function to get value for special parameter `SECONDS' */
+
+/**/
+zlong
+intsecondsgetfn(UNUSED(Param pm))
+{
+ struct timeval now;
+ struct timezone dummy_tz;
+
+ gettimeofday(&now, &dummy_tz);
+
+ return (zlong)(now.tv_sec - shtimer.tv_sec -
+ (now.tv_usec < shtimer.tv_usec ? 1 : 0));
+}
+
+/* Function to set value of special parameter `SECONDS' */
+
+/**/
+void
+intsecondssetfn(UNUSED(Param pm), zlong x)
+{
+ struct timeval now;
+ struct timezone dummy_tz;
+ zlong diff;
+
+ gettimeofday(&now, &dummy_tz);
+ diff = (zlong)now.tv_sec - x;
+ shtimer.tv_sec = diff;
+ if ((zlong)shtimer.tv_sec != diff)
+ zwarn("SECONDS truncated on assignment");
+ shtimer.tv_usec = now.tv_usec;
+}
+
+/**/
+double
+floatsecondsgetfn(UNUSED(Param pm))
+{
+ struct timeval now;
+ struct timezone dummy_tz;
+
+ gettimeofday(&now, &dummy_tz);
+
+ return (double)(now.tv_sec - shtimer.tv_sec) +
+ (double)(now.tv_usec - shtimer.tv_usec) / 1000000.0;
+}
+
+/**/
+void
+floatsecondssetfn(UNUSED(Param pm), double x)
+{
+ struct timeval now;
+ struct timezone dummy_tz;
+
+ gettimeofday(&now, &dummy_tz);
+ shtimer.tv_sec = now.tv_sec - (zlong)x;
+ shtimer.tv_usec = now.tv_usec - (zlong)((x - (zlong)x) * 1000000.0);
+}
+
+/**/
+double
+getrawseconds(void)
+{
+ return (double)shtimer.tv_sec + (double)shtimer.tv_usec / 1000000.0;
+}
+
+/**/
+void
+setrawseconds(double x)
+{
+ shtimer.tv_sec = (zlong)x;
+ shtimer.tv_usec = (zlong)((x - (zlong)x) * 1000000.0);
+}
+
+/**/
+int
+setsecondstype(Param pm, int on, int off)
+{
+ int newflags = (pm->node.flags | on) & ~off;
+ int tp = PM_TYPE(newflags);
+ /* Only one of the numeric types is allowed. */
+ if (tp == PM_EFLOAT || tp == PM_FFLOAT)
+ {
+ pm->gsu.f = &floatseconds_gsu;
+ }
+ else if (tp == PM_INTEGER)
+ {
+ pm->gsu.i = &intseconds_gsu;
+ }
+ else
+ return 1;
+ pm->node.flags = newflags;
+ return 0;
+}
+
+/* Function to get value for special parameter `USERNAME' */
+
+/**/
+char *
+usernamegetfn(UNUSED(Param pm))
+{
+ return get_username();
+}
+
+/* Function to set value of special parameter `USERNAME' */
+
+/**/
+void
+usernamesetfn(UNUSED(Param pm), char *x)
+{
+#if defined(HAVE_SETUID) && defined(HAVE_GETPWNAM)
+ struct passwd *pswd;
+
+ if (x && (pswd = getpwnam(x)) && (pswd->pw_uid != cached_uid)) {
+# ifdef USE_INITGROUPS
+ initgroups(x, pswd->pw_gid);
+# endif
+ if (setgid(pswd->pw_gid))
+ zwarn("failed to change group ID: %e", errno);
+ else if (setuid(pswd->pw_uid))
+ zwarn("failed to change user ID: %e", errno);
+ else {
+ zsfree(cached_username);
+ cached_username = ztrdup(pswd->pw_name);
+ cached_uid = pswd->pw_uid;
+ }
+ }
+#endif /* HAVE_SETUID && HAVE_GETPWNAM */
+ zsfree(x);
+}
+
+/* Function to get value for special parameter `UID' */
+
+/**/
+zlong
+uidgetfn(UNUSED(Param pm))
+{
+ return getuid();
+}
+
+/* Function to set value of special parameter `UID' */
+
+/**/
+void
+uidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETUID
+ if (setuid((uid_t)x))
+ zerr("failed to change user ID: %e", errno);
+#endif
+}
+
+/* Function to get value for special parameter `EUID' */
+
+/**/
+zlong
+euidgetfn(UNUSED(Param pm))
+{
+ return geteuid();
+}
+
+/* Function to set value of special parameter `EUID' */
+
+/**/
+void
+euidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETEUID
+ if (seteuid((uid_t)x))
+ zerr("failed to change effective user ID: %e", errno);
+#endif
+}
+
+/* Function to get value for special parameter `GID' */
+
+/**/
+zlong
+gidgetfn(UNUSED(Param pm))
+{
+ return getgid();
+}
+
+/* Function to set value of special parameter `GID' */
+
+/**/
+void
+gidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETUID
+ if (setgid((gid_t)x))
+ zerr("failed to change group ID: %e", errno);
+#endif
+}
+
+/* Function to get value for special parameter `EGID' */
+
+/**/
+zlong
+egidgetfn(UNUSED(Param pm))
+{
+ return getegid();
+}
+
+/* Function to set value of special parameter `EGID' */
+
+/**/
+void
+egidsetfn(UNUSED(Param pm), zlong x)
+{
+#ifdef HAVE_SETEUID
+ if (setegid((gid_t)x))
+ zerr("failed to change effective group ID: %e", errno);
+#endif
+}
+
+/**/
+zlong
+ttyidlegetfn(UNUSED(Param pm))
+{
+ struct stat ttystat;
+
+ if (SHTTY == -1 || fstat(SHTTY, &ttystat))
+ return -1;
+ return time(NULL) - ttystat.st_atime;
+}
+
+/* Function to get value for special parameter `IFS' */
+
+/**/
+char *
+ifsgetfn(UNUSED(Param pm))
+{
+ return ifs;
+}
+
+/* Function to set value of special parameter `IFS' */
+
+/**/
+void
+ifssetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(ifs);
+ ifs = x;
+ inittyptab();
+}
+
+/* Functions to set value of special parameters `LANG' and `LC_*' */
+
+#ifdef USE_LOCALE
+static struct localename {
+ char *name;
+ int category;
+} lc_names[] = {
+#ifdef LC_COLLATE
+ {"LC_COLLATE", LC_COLLATE},
+#endif
+#ifdef LC_CTYPE
+ {"LC_CTYPE", LC_CTYPE},
+#endif
+#ifdef LC_MESSAGES
+ {"LC_MESSAGES", LC_MESSAGES},
+#endif
+#ifdef LC_NUMERIC
+ {"LC_NUMERIC", LC_NUMERIC},
+#endif
+#ifdef LC_TIME
+ {"LC_TIME", LC_TIME},
+#endif
+ {NULL, 0}
+};
+
+/**/
+static void
+setlang(char *x)
+{
+ struct localename *ln;
+ char *x2;
+
+ if ((x2 = getsparam_u("LC_ALL")) && *x2)
+ return;
+
+ /*
+ * Set the global locale to the value passed, but override
+ * this with any non-empty definitions for specific
+ * categories.
+ *
+ * We only use non-empty definitions because empty values aren't
+ * valid as locales; when passed to setlocale() they mean "use the
+ * environment variable", but if that's what we're setting the value
+ * from this is meaningless. So just all $LANG to show through in
+ * that case.
+ */
+ setlocale(LC_ALL, x ? unmeta(x) : "");
+ queue_signals();
+ for (ln = lc_names; ln->name; ln++)
+ if ((x = getsparam_u(ln->name)) && *x)
+ setlocale(ln->category, x);
+ unqueue_signals();
+}
+
+/**/
+void
+lc_allsetfn(Param pm, char *x)
+{
+ strsetfn(pm, x);
+ /*
+ * Treat an empty LC_ALL the same as an unset one,
+ * namely by using LANG as the default locale but overriding
+ * that with any LC_* that are set.
+ */
+ if (!x || !*x) {
+ x = getsparam_u("LANG");
+ if (x && *x) {
+ queue_signals();
+ setlang(x);
+ unqueue_signals();
+ }
+ }
+ else
+ setlocale(LC_ALL, unmeta(x));
+}
+
+/**/
+void
+langsetfn(Param pm, char *x)
+{
+ strsetfn(pm, x);
+ setlang(unmeta(x));
+}
+
+/**/
+void
+lcsetfn(Param pm, char *x)
+{
+ char *x2;
+ struct localename *ln;
+
+ strsetfn(pm, x);
+ if ((x2 = getsparam("LC_ALL")) && *x2)
+ return;
+ queue_signals();
+ /* Treat empty LC_* the same as unset. */
+ if (!x || !*x)
+ x = getsparam("LANG");
+
+ /*
+ * If we've got no non-empty string at this
+ * point (after checking $LANG, too),
+ * we shouldn't bother setting anything.
+ */
+ if (x && *x) {
+ for (ln = lc_names; ln->name; ln++)
+ if (!strcmp(ln->name, pm->node.nam))
+ setlocale(ln->category, unmeta(x));
+ }
+ unqueue_signals();
+}
+#endif /* USE_LOCALE */
+
+/* Function to set value for special parameter `0' */
+
+/**/
+static void
+argzerosetfn(UNUSED(Param pm), char *x)
+{
+ if (x) {
+ if (isset(POSIXARGZERO))
+ zerr("read-only variable: 0");
+ else {
+ zsfree(argzero);
+ argzero = ztrdup(x);
+ }
+ zsfree(x);
+ }
+}
+
+/* Function to get value for special parameter `0' */
+
+/**/
+static char *
+argzerogetfn(UNUSED(Param pm))
+{
+ if (isset(POSIXARGZERO))
+ return posixzero;
+ return argzero;
+}
+
+/* Function to get value for special parameter `HISTSIZE' */
+
+/**/
+zlong
+histsizegetfn(UNUSED(Param pm))
+{
+ return histsiz;
+}
+
+/* Function to set value of special parameter `HISTSIZE' */
+
+/**/
+void
+histsizesetfn(UNUSED(Param pm), zlong v)
+{
+ if ((histsiz = v) < 1)
+ histsiz = 1;
+ resizehistents();
+}
+
+/* Function to get value for special parameter `SAVEHIST' */
+
+/**/
+zlong
+savehistsizegetfn(UNUSED(Param pm))
+{
+ return savehistsiz;
+}
+
+/* Function to set value of special parameter `SAVEHIST' */
+
+/**/
+void
+savehistsizesetfn(UNUSED(Param pm), zlong v)
+{
+ if ((savehistsiz = v) < 0)
+ savehistsiz = 0;
+}
+
+/* Function to set value for special parameter `ERRNO' */
+
+/**/
+void
+errnosetfn(UNUSED(Param pm), zlong x)
+{
+ errno = (int)x;
+ if ((zlong)errno != x)
+ zwarn("errno truncated on assignment");
+}
+
+/* Function to get value for special parameter `ERRNO' */
+
+/**/
+zlong
+errnogetfn(UNUSED(Param pm))
+{
+ return errno;
+}
+
+/* Function to get value for special parameter `KEYBOARD_HACK' */
+
+/**/
+char *
+keyboardhackgetfn(UNUSED(Param pm))
+{
+ static char buf[2];
+
+ buf[0] = keyboardhackchar;
+ buf[1] = '\0';
+ return buf;
+}
+
+
+/* Function to set value of special parameter `KEYBOARD_HACK' */
+
+/**/
+void
+keyboardhacksetfn(UNUSED(Param pm), char *x)
+{
+ if (x) {
+ int len, i;
+
+ unmetafy(x, &len);
+ if (len > 1) {
+ len = 1;
+ zwarn("Only one KEYBOARD_HACK character can be defined"); /* could be changed if needed */
+ }
+ for (i = 0; i < len; i++) {
+ if (!isascii(STOUC(x[i]))) {
+ zwarn("KEYBOARD_HACK can only contain ASCII characters");
+ return;
+ }
+ }
+ keyboardhackchar = len ? STOUC(x[0]) : '\0';
+ free(x);
+ } else
+ keyboardhackchar = '\0';
+}
+
+/* Function to get value for special parameter `histchar' */
+
+/**/
+char *
+histcharsgetfn(UNUSED(Param pm))
+{
+ static char buf[4];
+
+ buf[0] = bangchar;
+ buf[1] = hatchar;
+ buf[2] = hashchar;
+ buf[3] = '\0';
+ return buf;
+}
+
+/* Function to set value of special parameter `histchar' */
+
+/**/
+void
+histcharssetfn(UNUSED(Param pm), char *x)
+{
+ if (x) {
+ int len, i;
+
+ unmetafy(x, &len);
+ if (len > 3)
+ len = 3;
+ for (i = 0; i < len; i++) {
+ if (!isascii(STOUC(x[i]))) {
+ zwarn("HISTCHARS can only contain ASCII characters");
+ return;
+ }
+ }
+ bangchar = len ? STOUC(x[0]) : '\0';
+ hatchar = len > 1 ? STOUC(x[1]) : '\0';
+ hashchar = len > 2 ? STOUC(x[2]) : '\0';
+ free(x);
+ } else {
+ bangchar = '!';
+ hashchar = '#';
+ hatchar = '^';
+ }
+ inittyptab();
+}
+
+/* Function to get value for special parameter `HOME' */
+
+/**/
+char *
+homegetfn(UNUSED(Param pm))
+{
+ return home;
+}
+
+/* Function to set value of special parameter `HOME' */
+
+/**/
+void
+homesetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(home);
+ if (x && isset(CHASELINKS) && (home = xsymlink(x, 0)))
+ zsfree(x);
+ else
+ home = x ? x : ztrdup("");
+ finddir(NULL);
+}
+
+/* Function to get value for special parameter `WORDCHARS' */
+
+/**/
+char *
+wordcharsgetfn(UNUSED(Param pm))
+{
+ return wordchars;
+}
+
+/* Function to set value of special parameter `WORDCHARS' */
+
+/**/
+void
+wordcharssetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(wordchars);
+ wordchars = x;
+ inittyptab();
+}
+
+/* Function to get value for special parameter `_' */
+
+/**/
+char *
+underscoregetfn(UNUSED(Param pm))
+{
+ char *u = dupstring(zunderscore);
+
+ untokenize(u);
+ return u;
+}
+
+/* Function used when we need to reinitialise the terminal */
+
+static void
+term_reinit_from_pm(void)
+{
+ /* If non-interactive, delay setting up term till we need it. */
+ if (unset(INTERACTIVE) || !*term)
+ termflags |= TERM_UNKNOWN;
+ else
+ init_term();
+}
+
+/* Function to get value for special parameter `TERM' */
+
+/**/
+char *
+termgetfn(UNUSED(Param pm))
+{
+ return term;
+}
+
+/* Function to set value of special parameter `TERM' */
+
+/**/
+void
+termsetfn(UNUSED(Param pm), char *x)
+{
+ zsfree(term);
+ term = x ? x : ztrdup("");
+ term_reinit_from_pm();
+}
+
+/* Function to get value of special parameter `TERMINFO' */
+
+/**/
+char *
+terminfogetfn(UNUSED(Param pm))
+{
+ return zsh_terminfo ? zsh_terminfo : dupstring("");
+}
+
+/* Function to set value of special parameter `TERMINFO' */
+
+/**/
+void
+terminfosetfn(Param pm, char *x)
+{
+ zsfree(zsh_terminfo);
+ zsh_terminfo = x;
+
+ /*
+ * terminfo relies on the value being exported before
+ * we reinitialise the terminal. This is a bit inefficient.
+ */
+ if ((pm->node.flags & PM_EXPORTED) && x)
+ addenv(pm, x);
+
+ term_reinit_from_pm();
+}
+
+/* Function to get value of special parameter `TERMINFO_DIRS' */
+
+/**/
+char *
+terminfodirsgetfn(UNUSED(Param pm))
+{
+ return zsh_terminfodirs ? zsh_terminfodirs : dupstring("");
+}
+
+/* Function to set value of special parameter `TERMINFO_DIRS' */
+
+/**/
+void
+terminfodirssetfn(Param pm, char *x)
+{
+ zsfree(zsh_terminfodirs);
+ zsh_terminfodirs = x;
+
+ /*
+ * terminfo relies on the value being exported before
+ * we reinitialise the terminal. This is a bit inefficient.
+ */
+ if ((pm->node.flags & PM_EXPORTED) && x)
+ addenv(pm, x);
+
+ term_reinit_from_pm();
+}
+/* Function to get value for special parameter `pipestatus' */
+
+/**/
+static char **
+pipestatgetfn(UNUSED(Param pm))
+{
+ char **x = (char **) zhalloc((numpipestats + 1) * sizeof(char *));
+ char buf[DIGBUFSIZE], **p;
+ int *q, i;
+
+ for (p = x, q = pipestats, i = numpipestats; i--; p++, q++) {
+ sprintf(buf, "%d", *q);
+ *p = dupstring(buf);
+ }
+ *p = NULL;
+
+ return x;
+}
+
+/* Function to get value for special parameter `pipestatus' */
+
+/**/
+static void
+pipestatsetfn(UNUSED(Param pm), char **x)
+{
+ if (x) {
+ int i;
+
+ for (i = 0; *x && i < MAX_PIPESTATS; i++, x++)
+ pipestats[i] = atoi(*x);
+ numpipestats = i;
+ }
+ else
+ numpipestats = 0;
+}
+
+/**/
+void
+arrfixenv(char *s, char **t)
+{
+ Param pm;
+ int joinchar;
+
+ if (t == path)
+ cmdnamtab->emptytable(cmdnamtab);
+
+ pm = (Param) paramtab->getnode(paramtab, s);
+
+ /*
+ * Only one level of a parameter can be exported. Unless
+ * ALLEXPORT is set, this must be global.
+ */
+
+ if (pm->node.flags & PM_HASHELEM)
+ return;
+
+ if (isset(ALLEXPORT))
+ pm->node.flags |= PM_EXPORTED;
+
+ /*
+ * Do not "fix" parameters that were not exported
+ */
+
+ if (!(pm->node.flags & PM_EXPORTED))
+ return;
+
+ if (pm->node.flags & PM_TIED)
+ joinchar = STOUC(((struct tieddata *)pm->u.data)->joinchar);
+ else
+ joinchar = ':';
+
+ addenv(pm, t ? zjoin(t, joinchar, 1) : "");
+}
+
+
+/**/
+int
+zputenv(char *str)
+{
+ DPUTS(!str, "Attempt to put null string into environment.");
+#ifdef USE_SET_UNSET_ENV
+ /*
+ * If we are using unsetenv() to remove values from the
+ * environment, which is the safe thing to do, we
+ * need to use setenv() to put them there in the first place.
+ * Unfortunately this is a slightly different interface
+ * from what zputenv() assumes.
+ */
+ char *ptr;
+ int ret;
+
+ for (ptr = str; *ptr && STOUC(*ptr) < 128 && *ptr != '='; ptr++)
+ ;
+ if (STOUC(*ptr) >= 128) {
+ /*
+ * Environment variables not in the portable character
+ * set are non-standard and we don't really know of
+ * a use for them.
+ *
+ * We'll disable until someone complains.
+ */
+ return 1;
+ } else if (*ptr) {
+ *ptr = '\0';
+ ret = setenv(str, ptr+1, 1);
+ *ptr = '=';
+ } else {
+ /* safety first */
+ DPUTS(1, "bad environment string");
+ ret = setenv(str, ptr, 1);
+ }
+ return ret;
+#else
+#ifdef HAVE_PUTENV
+ return putenv(str);
+#else
+ char **ep;
+ int num_env;
+
+
+ /* First check if there is already an environment *
+ * variable matching string `name'. */
+ if (findenv(str, &num_env)) {
+ environ[num_env] = str;
+ } else {
+ /* Else we have to make room and add it */
+ num_env = arrlen(environ);
+ environ = (char **) zrealloc(environ, (sizeof(char *)) * (num_env + 2));
+
+ /* Now add it at the end */
+ ep = environ + num_env;
+ *ep = str;
+ *(ep + 1) = NULL;
+ }
+ return 0;
+#endif
+#endif
+}
+
+/**/
+#ifndef USE_SET_UNSET_ENV
+/**/
+static int
+findenv(char *name, int *pos)
+{
+ char **ep, *eq;
+ int nlen;
+
+
+ eq = strchr(name, '=');
+ nlen = eq ? eq - name : (int)strlen(name);
+ for (ep = environ; *ep; ep++)
+ if (!strncmp (*ep, name, nlen) && *((*ep)+nlen) == '=') {
+ if (pos)
+ *pos = ep - environ;
+ return 1;
+ }
+
+ return 0;
+}
+/**/
+#endif
+
+/* Given *name = "foo", it searches the environment for string *
+ * "foo=bar", and returns a pointer to the beginning of "bar" */
+
+/**/
+mod_export char *
+zgetenv(char *name)
+{
+#ifdef HAVE_GETENV
+ return getenv(name);
+#else
+ char **ep, *s, *t;
+
+ for (ep = environ; *ep; ep++) {
+ for (s = *ep, t = name; *s && *s == *t; s++, t++);
+ if (*s == '=' && !*t)
+ return s + 1;
+ }
+ return NULL;
+#endif
+}
+
+/**/
+static void
+copyenvstr(char *s, char *value, int flags)
+{
+ while (*s++) {
+ if ((*s = *value++) == Meta)
+ *s = *value++ ^ 32;
+ if (flags & PM_LOWER)
+ *s = tulower(*s);
+ else if (flags & PM_UPPER)
+ *s = tuupper(*s);
+ }
+}
+
+/**/
+void
+addenv(Param pm, char *value)
+{
+ char *newenv = 0;
+#ifndef USE_SET_UNSET_ENV
+ char *oldenv = 0, *env = 0;
+ int pos;
+
+ /*
+ * First check if there is already an environment
+ * variable matching string `name'.
+ */
+ if (findenv(pm->node.nam, &pos))
+ oldenv = environ[pos];
+#endif
+
+ newenv = mkenvstr(pm->node.nam, value, pm->node.flags);
+ if (zputenv(newenv)) {
+ zsfree(newenv);
+ pm->env = NULL;
+ return;
+ }
+#ifdef USE_SET_UNSET_ENV
+ /*
+ * If we are using setenv/unsetenv to manage the environment,
+ * we simply store the string we created in pm->env since
+ * memory management of the environment is handled entirely
+ * by the system.
+ *
+ * TODO: is this good enough to fix problem cases from
+ * the other branch? If so, we don't actually need to
+ * store pm->env at all, just a flag that the value was set.
+ */
+ if (pm->env)
+ zsfree(pm->env);
+ pm->env = newenv;
+ pm->node.flags |= PM_EXPORTED;
+#else
+ /*
+ * Under Cygwin we must use putenv() to maintain consistency.
+ * Unfortunately, current version (1.1.2) copies argument and may
+ * silently reuse existing environment string. This tries to
+ * check for both cases
+ */
+ if (findenv(pm->node.nam, &pos)) {
+ env = environ[pos];
+ if (env != oldenv)
+ zsfree(oldenv);
+ if (env != newenv)
+ zsfree(newenv);
+ pm->node.flags |= PM_EXPORTED;
+ pm->env = env;
+ return;
+ }
+
+ DPUTS(1, "addenv should never reach the end");
+ pm->env = NULL;
+#endif
+}
+
+
+/* Given strings *name = "foo", *value = "bar", *
+ * return a new string *str = "foo=bar". */
+
+/**/
+static char *
+mkenvstr(char *name, char *value, int flags)
+{
+ char *str, *s = value;
+ int len_name, len_value = 0;
+
+ len_name = strlen(name);
+ if (s)
+ while (*s && (*s++ != Meta || *s++ != 32))
+ len_value++;
+ s = str = (char *) zalloc(len_name + len_value + 2);
+ strcpy(s, name);
+ s += len_name;
+ *s = '=';
+ if (value)
+ copyenvstr(s, value, flags);
+ else
+ *++s = '\0';
+ return str;
+}
+
+/* Given *name = "foo", *value = "bar", add the *
+ * string "foo=bar" to the environment. Return a *
+ * pointer to the location of this new environment *
+ * string. */
+
+
+#ifndef USE_SET_UNSET_ENV
+/**/
+void
+delenvvalue(char *x)
+{
+ char **ep;
+
+ for (ep = environ; *ep; ep++) {
+ if (*ep == x)
+ break;
+ }
+ if (*ep) {
+ for (; (ep[0] = ep[1]); ep++);
+ }
+ zsfree(x);
+}
+#endif
+
+
+/* Delete a pointer from the list of pointers to environment *
+ * variables by shifting all the other pointers up one slot. */
+
+/**/
+void
+delenv(Param pm)
+{
+#ifdef USE_SET_UNSET_ENV
+ unsetenv(pm->node.nam);
+ zsfree(pm->env);
+#else
+ delenvvalue(pm->env);
+#endif
+ pm->env = NULL;
+ /*
+ * Note we don't remove PM_EXPORT from the flags. This
+ * may be asking for trouble but we need to know later
+ * if we restore this parameter to its old value.
+ */
+}
+
+/*
+ * Guts of convbase: this version can return the number of digits
+ * sans any base discriminator.
+ */
+
+/**/
+void
+convbase_ptr(char *s, zlong v, int base, int *ndigits)
+{
+ int digs = 0;
+ zulong x;
+
+ if (v < 0)
+ *s++ = '-', v = -v;
+ if (base >= -1 && base <= 1)
+ base = -10;
+
+ if (base > 0) {
+ if (isset(CBASES) && base == 16)
+ sprintf(s, "0x");
+ else if (isset(CBASES) && base == 8 && isset(OCTALZEROES))
+ sprintf(s, "0");
+ else if (base != 10)
+ sprintf(s, "%d#", base);
+ else
+ *s = 0;
+ s += strlen(s);
+ } else
+ base = -base;
+ for (x = v; x; digs++)
+ x /= base;
+ if (!digs)
+ digs = 1;
+ if (ndigits)
+ *ndigits = digs;
+ s[digs--] = '\0';
+ x = v;
+ while (digs >= 0) {
+ int dig = x % base;
+
+ s[digs--] = (dig < 10) ? '0' + dig : dig - 10 + 'A';
+ x /= base;
+ }
+}
+
+/*
+ * Basic conversion of integer to a string given a base.
+ * If 0 base is 10.
+ * If negative no base discriminator is output.
+ */
+
+/**/
+mod_export void
+convbase(char *s, zlong v, int base)
+{
+ convbase_ptr(s, v, base, NULL);
+}
+
+/*
+ * Add underscores to converted integer for readability with given spacing.
+ * s is as for convbase: at least BDIGBUFSIZE.
+ * If underscores were added, returned value with underscores comes from
+ * heap, else the returned value is s.
+ */
+
+/**/
+char *
+convbase_underscore(char *s, zlong v, int base, int underscore)
+{
+ char *retptr, *sptr, *dptr;
+ int ndigits, nunderscore, mod, len;
+
+ convbase_ptr(s, v, base, &ndigits);
+
+ if (underscore <= 0)
+ return s;
+
+ nunderscore = (ndigits - 1) / underscore;
+ if (!nunderscore)
+ return s;
+ len = strlen(s);
+ retptr = zhalloc(len + nunderscore + 1);
+ mod = 0;
+ memcpy(retptr, s, len - ndigits);
+ sptr = s + len;
+ dptr = retptr + len + nunderscore;
+ /* copy the null */
+ *dptr-- = *sptr--;
+ for (;;) {
+ *dptr = *sptr;
+ if (!--ndigits)
+ break;
+ dptr--;
+ sptr--;
+ if (++mod == underscore) {
+ mod = 0;
+ *dptr-- = '_';
+ }
+ }
+
+ return retptr;
+}
+
+/*
+ * Convert a floating point value for output.
+ * Unlike convbase(), this has its own internal storage and returns
+ * a value from the heap.
+ */
+
+/**/
+char *
+convfloat(double dval, int digits, int flags, FILE *fout)
+{
+ char fmt[] = "%.*e";
+ char *prev_locale, *ret;
+
+ /*
+ * The difficulty with the buffer size is that a %f conversion
+ * prints all digits before the decimal point: with 64 bit doubles,
+ * that's around 310. We can't check without doing some quite
+ * serious floating point operations we'd like to avoid.
+ * Then we are liable to get all the digits
+ * we asked for after the decimal point, or we should at least
+ * bargain for it. So we just allocate 512 + digits. This
+ * should work until somebody decides on 128-bit doubles.
+ */
+ if (!(flags & (PM_EFLOAT|PM_FFLOAT))) {
+ /*
+ * Conversion from a floating point expression without using
+ * a variable. The best bet in this case just seems to be
+ * to use the general %g format with something like the maximum
+ * double precision.
+ */
+ fmt[3] = 'g';
+ if (!digits)
+ digits = 17;
+ } else {
+ if (flags & PM_FFLOAT)
+ fmt[3] = 'f';
+ if (digits <= 0)
+ digits = 10;
+ if (flags & PM_EFLOAT) {
+ /*
+ * Here, we are given the number of significant figures, but
+ * %e wants the number of decimal places (unlike %g)
+ */
+ digits--;
+ }
+ }
+#ifdef USE_LOCALE
+ prev_locale = dupstring(setlocale(LC_NUMERIC, NULL));
+ setlocale(LC_NUMERIC, "POSIX");
+#endif
+ if (fout) {
+ fprintf(fout, fmt, digits, dval);
+ ret = NULL;
+ } else {
+ VARARR(char, buf, 512 + digits);
+ if (isinf(dval))
+ ret = dupstring((dval < 0.0) ? "-Inf" : "Inf");
+ else if (isnan(dval))
+ ret = dupstring("NaN");
+ else {
+ sprintf(buf, fmt, digits, dval);
+ if (!strchr(buf, 'e') && !strchr(buf, '.'))
+ strcat(buf, ".");
+ ret = dupstring(buf);
+ }
+ }
+#ifdef USE_LOCALE
+ if (prev_locale) setlocale(LC_NUMERIC, prev_locale);
+#endif
+ return ret;
+}
+
+/*
+ * convert float to string with basic options but inserting underscores
+ * for readability.
+ */
+
+/**/
+char *convfloat_underscore(double dval, int underscore)
+{
+ int ndigits_int = 0, ndigits_frac = 0, nunderscore, len;
+ char *s, *retptr, *sptr, *dptr;
+
+ s = convfloat(dval, 0, 0, NULL);
+ if (underscore <= 0)
+ return s;
+
+ /*
+ * Count the number of digits before and after the decimal point, if any.
+ */
+ sptr = s;
+ if (*sptr == '-')
+ sptr++;
+ while (idigit(*sptr)) {
+ ndigits_int++;
+ sptr++;
+ }
+ if (*sptr == '.') {
+ sptr++;
+ while (idigit(*sptr)) {
+ ndigits_frac++;
+ sptr++;
+ }
+ }
+
+ /*
+ * Work out how many underscores to insert --- remember we
+ * put them in integer and fractional parts separately.
+ */
+ nunderscore = (ndigits_int-1) / underscore + (ndigits_frac-1) / underscore;
+ if (!nunderscore)
+ return s;
+ len = strlen(s);
+ dptr = retptr = zhalloc(len + nunderscore + 1);
+
+ /*
+ * Insert underscores in integer part.
+ * Grouping starts from the point in both directions.
+ */
+ sptr = s;
+ if (*sptr == '-')
+ *dptr++ = *sptr++;
+ while (ndigits_int) {
+ *dptr++ = *sptr++;
+ if (--ndigits_int && !(ndigits_int % underscore))
+ *dptr++ = '_';
+ }
+ if (ndigits_frac) {
+ /*
+ * Insert underscores in the fractional part.
+ */
+ int mod = 0;
+ /* decimal point, we already checked */
+ *dptr++ = *sptr++;
+ while (ndigits_frac) {
+ *dptr++ = *sptr++;
+ mod++;
+ if (--ndigits_frac && mod == underscore) {
+ *dptr++ = '_';
+ mod = 0;
+ }
+ }
+ }
+ /* Copy exponent and anything else up to null */
+ while ((*dptr++ = *sptr++))
+ ;
+ return retptr;
+}
+
+/* Start a parameter scope */
+
+/**/
+mod_export void
+startparamscope(void)
+{
+ locallevel++;
+}
+
+/* End a parameter scope: delete the parameters local to the scope. */
+
+/**/
+mod_export void
+endparamscope(void)
+{
+ queue_signals();
+ locallevel--;
+ /* This pops anything from a higher locallevel */
+ saveandpophiststack(0, HFILE_USE_OPTIONS);
+ scanhashtable(paramtab, 0, 0, 0, scanendscope, 0);
+ unqueue_signals();
+}
+
+/**/
+static void
+scanendscope(HashNode hn, UNUSED(int flags))
+{
+ Param pm = (Param)hn;
+ if (pm->level > locallevel) {
+ if ((pm->node.flags & (PM_SPECIAL|PM_REMOVABLE)) == PM_SPECIAL) {
+ /*
+ * Removable specials are normal in that they can be removed
+ * to reveal an ordinary parameter beneath. Here we handle
+ * non-removable specials, which were made local by stealth
+ * (see newspecial code in typeset_single()). In fact the
+ * visible pm is always the same struct; the pm->old is
+ * just a place holder for old data and flags.
+ */
+ Param tpm = pm->old;
+
+ if (!strcmp(pm->node.nam, "SECONDS"))
+ {
+ setsecondstype(pm, PM_TYPE(tpm->node.flags), PM_TYPE(pm->node.flags));
+ /*
+ * We restore SECONDS by restoring its raw internal value
+ * that we cached off into tpm->u.dval.
+ */
+ setrawseconds(tpm->u.dval);
+ tpm->node.flags |= PM_NORESTORE;
+ }
+ DPUTS(!tpm || PM_TYPE(pm->node.flags) != PM_TYPE(tpm->node.flags) ||
+ !(tpm->node.flags & PM_SPECIAL),
+ "BUG: in restoring scope of special parameter");
+ pm->old = tpm->old;
+ pm->node.flags = (tpm->node.flags & ~PM_NORESTORE);
+ pm->level = tpm->level;
+ pm->base = tpm->base;
+ pm->width = tpm->width;
+ if (pm->env)
+ delenv(pm);
+
+ 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);
+ break;
+ case PM_INTEGER:
+ pm->gsu.i->setfn(pm, tpm->u.val);
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ pm->gsu.f->setfn(pm, tpm->u.dval);
+ break;
+ case PM_ARRAY:
+ pm->gsu.a->setfn(pm, tpm->u.arr);
+ break;
+ case PM_HASHED:
+ pm->gsu.h->setfn(pm, tpm->u.hash);
+ break;
+ }
+ zfree(tpm, sizeof(*tpm));
+
+ if (pm->node.flags & PM_EXPORTED)
+ export_param(pm);
+ } else
+ unsetparam_pm(pm, 0, 0);
+ }
+}
+
+
+/**********************************/
+/* Parameter Hash Table Functions */
+/**********************************/
+
+/**/
+void
+freeparamnode(HashNode hn)
+{
+ Param pm = (Param) hn;
+
+ /* The second argument of unsetfn() is used by modules to
+ * differentiate "exp"licit unset from implicit unset, as when
+ * a parameter is going out of scope. It's not clear which
+ * of these applies here, but passing 1 has always worked.
+ */
+ if (delunset)
+ pm->gsu.s->unsetfn(pm, 1);
+ zsfree(pm->node.nam);
+ /* If this variable was tied by the user, ename was ztrdup'd */
+ if (pm->node.flags & PM_TIED)
+ zsfree(pm->ename);
+ zfree(pm, sizeof(struct param));
+}
+
+/* Print a parameter */
+
+enum paramtypes_flags {
+ PMTF_USE_BASE = (1<<0),
+ PMTF_USE_WIDTH = (1<<1),
+ PMTF_TEST_LEVEL = (1<<2)
+};
+
+struct paramtypes {
+ int binflag; /* The relevant PM_FLAG(S) */
+ const char *string; /* String for verbose output */
+ int typeflag; /* Flag for typeset -? */
+ int flags; /* The enum above */
+};
+
+static const struct paramtypes pmtypes[] = {
+ { PM_AUTOLOAD, "undefined", 0, 0},
+ { PM_INTEGER, "integer", 'i', PMTF_USE_BASE},
+ { PM_EFLOAT, "float", 'E', 0},
+ { PM_FFLOAT, "float", 'F', 0},
+ { PM_ARRAY, "array", 'a', 0},
+ { PM_HASHED, "association", 'A', 0},
+ { 0, "local", 0, PMTF_TEST_LEVEL},
+ { PM_LEFT, "left justified", 'L', PMTF_USE_WIDTH},
+ { PM_RIGHT_B, "right justified", 'R', PMTF_USE_WIDTH},
+ { PM_RIGHT_Z, "zero filled", 'Z', PMTF_USE_WIDTH},
+ { PM_LOWER, "lowercase", 'l', 0},
+ { PM_UPPER, "uppercase", 'u', 0},
+ { PM_READONLY, "readonly", 'r', 0},
+ { PM_TAGGED, "tagged", 't', 0},
+ { PM_EXPORTED, "exported", 'x', 0}
+};
+
+#define PMTYPES_SIZE ((int)(sizeof(pmtypes)/sizeof(struct paramtypes)))
+
+static void
+printparamvalue(Param p, int printflags)
+{
+ char *t, **u;
+
+ if (!(printflags & PRINT_KV_PAIR))
+ putchar('=');
+
+ /* How the value is displayed depends *
+ * on the type of the parameter */
+ switch (PM_TYPE(p->node.flags)) {
+ case PM_SCALAR:
+ /* string: simple output */
+ if (p->gsu.s->getfn && (t = p->gsu.s->getfn(p)))
+ quotedzputs(t, stdout);
+ break;
+ case PM_INTEGER:
+ /* integer */
+#ifdef ZSH_64_BIT_TYPE
+ fputs(output64(p->gsu.i->getfn(p)), stdout);
+#else
+ printf("%ld", p->gsu.i->getfn(p));
+#endif
+ break;
+ case PM_EFLOAT:
+ case PM_FFLOAT:
+ /* float */
+ convfloat(p->gsu.f->getfn(p), p->base, p->node.flags, stdout);
+ break;
+ case PM_ARRAY:
+ /* array */
+ if (!(printflags & PRINT_KV_PAIR)) {
+ putchar('(');
+ if (!(printflags & PRINT_LINE))
+ putchar(' ');
+ }
+ u = p->gsu.a->getfn(p);
+ if(*u) {
+ if (printflags & PRINT_LINE) {
+ if (printflags & PRINT_KV_PAIR)
+ printf(" ");
+ else
+ printf("\n ");
+ }
+ quotedzputs(*u++, stdout);
+ while (*u) {
+ if (printflags & PRINT_LINE)
+ printf("\n ");
+ else
+ putchar(' ');
+ quotedzputs(*u++, stdout);
+ }
+ if ((printflags & (PRINT_LINE|PRINT_KV_PAIR)) == PRINT_LINE)
+ putchar('\n');
+ }
+ if (!(printflags & PRINT_KV_PAIR)) {
+ if (!(printflags & PRINT_LINE))
+ putchar(' ');
+ putchar(')');
+ }
+ break;
+ case PM_HASHED:
+ /* association */
+ {
+ HashTable ht;
+ int found = 0;
+ if (!(printflags & PRINT_KV_PAIR)) {
+ putchar('(');
+ if (!(printflags & PRINT_LINE))
+ putchar(' ');
+ }
+ ht = p->gsu.h->getfn(p);
+ if (ht)
+ found = scanhashtable(ht, 1, 0, PM_UNSET,
+ ht->printnode, PRINT_KV_PAIR |
+ (printflags & PRINT_LINE));
+ if (!(printflags & PRINT_KV_PAIR)) {
+ if (found && (printflags & PRINT_LINE))
+ putchar('\n');
+ putchar(')');
+ }
+ }
+ break;
+ }
+ if ((printflags & (PRINT_KV_PAIR|PRINT_LINE)) == PRINT_KV_PAIR)
+ putchar(' ');
+ else if (!(printflags & PRINT_KV_PAIR))
+ putchar('\n');
+}
+
+/**/
+mod_export void
+printparamnode(HashNode hn, int printflags)
+{
+ Param p = (Param) hn;
+
+ if (p->node.flags & PM_UNSET) {
+ if (isset(POSIXBUILTINS) && (p->node.flags & PM_READONLY) &&
+ (printflags & PRINT_TYPESET))
+ {
+ /*
+ * Special POSIX rules: show the parameter as readonly
+ * even though it's unset, but with no value.
+ */
+ printflags |= PRINT_NAMEONLY;
+ }
+ else if (p->node.flags & PM_EXPORTED)
+ printflags |= PRINT_NAMEONLY;
+ else
+ return;
+ }
+ if (p->node.flags & PM_AUTOLOAD)
+ printflags |= PRINT_NAMEONLY;
+
+ if (printflags & PRINT_TYPESET) {
+ if ((p->node.flags & (PM_READONLY|PM_SPECIAL)) ==
+ (PM_READONLY|PM_SPECIAL) ||
+ (p->node.flags & PM_AUTOLOAD)) {
+ /*
+ * It's not possible to restore the state of
+ * these, so don't output.
+ */
+ return;
+ }
+ if (locallevel && p->level >= locallevel) {
+ printf("typeset "); /* printf("local "); */
+ } else if ((p->node.flags & PM_EXPORTED) &&
+ !(p->node.flags & (PM_ARRAY|PM_HASHED))) {
+ printf("export ");
+ } else if (locallevel) {
+ printf("typeset -g ");
+ } else
+ printf("typeset ");
+ }
+
+ /* Print the attributes of the parameter */
+ if (printflags & (PRINT_TYPE|PRINT_TYPESET)) {
+ int doneminus = 0, i;
+ const struct paramtypes *pmptr;
+
+ for (pmptr = pmtypes, i = 0; i < PMTYPES_SIZE; i++, pmptr++) {
+ int doprint = 0;
+ if (pmptr->flags & PMTF_TEST_LEVEL) {
+ if (p->level)
+ doprint = 1;
+ } else if ((pmptr->binflag != PM_EXPORTED || p->level ||
+ (p->node.flags & (PM_LOCAL|PM_ARRAY|PM_HASHED))) &&
+ (p->node.flags & pmptr->binflag))
+ doprint = 1;
+
+ if (doprint) {
+ if (printflags & PRINT_TYPESET) {
+ if (pmptr->typeflag) {
+ if (!doneminus) {
+ putchar('-');
+ doneminus = 1;
+ }
+ putchar(pmptr->typeflag);
+ }
+ } else
+ printf("%s ", pmptr->string);
+ if ((pmptr->flags & PMTF_USE_BASE) && p->base) {
+ printf("%d ", p->base);
+ doneminus = 0;
+ }
+ if ((pmptr->flags & PMTF_USE_WIDTH) && p->width) {
+ printf("%d ", p->width);
+ doneminus = 0;
+ }
+ }
+ }
+ if (doneminus)
+ putchar(' ');
+ }
+
+ if ((printflags & PRINT_NAMEONLY) ||
+ ((p->node.flags & PM_HIDEVAL) && !(printflags & PRINT_INCLUDEVALUE))) {
+ zputs(p->node.nam, stdout);
+ putchar('\n');
+ } else {
+ if (printflags & PRINT_KV_PAIR) {
+ if (printflags & PRINT_LINE)
+ printf("\n ");
+ putchar('[');
+ }
+ quotedzputs(p->node.nam, stdout);
+ if (printflags & PRINT_KV_PAIR)
+ printf("]=");
+
+ printparamvalue(p, printflags);
+ }
+}