Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

[PATCH 3/3] The zsh/ksh93 module



This module adds the "nameref" builtin command and implements most of
the predefined parameters in the "sh" namespace, for example
${.sh.lineno} and ${.sh.version}.  The previous patch to implement
PM_SPECIAL named references is required.

Where possible, these parameters are named references to the
corresponding zsh parameters, so ${.sh.version} is another name for
${ZSH_PATCHLEVEL}, etc.  Many of the remaining parameters are enabled
only in ksh emulation (as described in the documentation for the
module), to avoid function wrapper overhead in native mode.

I've included a documentation section describing some related features
that are NOT implemented, such as parameter discipline functions.  The
${.sh.name} and ${.sh.value} parameters are instead supplied for use
in user-defined widgets when "vared" is invoked.

There's a small change to utils.c to explicitly allow namespace
parameter syntax in ksh emulation mode.  We should perhaps reconsider
whether POSIX_IDENTIFIERS is really appropriate to be on by default in
ksh mode.
diff --git a/Doc/Makefile.in b/Doc/Makefile.in
index 23e5fc7e2..136b080d6 100644
--- a/Doc/Makefile.in
+++ b/Doc/Makefile.in
@@ -63,7 +63,7 @@ Zsh/mod_compctl.yo Zsh/mod_complete.yo Zsh/mod_complist.yo \
 Zsh/mod_computil.yo Zsh/mod_curses.yo \
 Zsh/mod_datetime.yo Zsh/mod_db_gdbm.yo Zsh/mod_deltochar.yo \
 Zsh/mod_example.yo Zsh/mod_files.yo Zsh/mod_langinfo.yo \
-Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo \
+Zsh/mod_ksh93.yo Zsh/mod_mapfile.yo Zsh/mod_mathfunc.yo \
 Zsh/mod_nearcolor.yo Zsh/mod_newuser.yo \
 Zsh/mod_parameter.yo Zsh/mod_pcre.yo Zsh/mod_private.yo \
 Zsh/mod_regex.yo Zsh/mod_sched.yo Zsh/mod_socket.yo \
diff --git a/Doc/Zsh/mod_ksh93.yo b/Doc/Zsh/mod_ksh93.yo
new file mode 100644
index 000000000..3ae644bd1
--- /dev/null
+++ b/Doc/Zsh/mod_ksh93.yo
@@ -0,0 +1,211 @@
+COMMENT(!MOD!zsh/ksh93
+Extended ksh93 compatibility for "emulate ksh"
+!MOD!)
+cindex(ksh93)
+The tt(zsh/ksh93) module provides one builtin and several parameters to
+improve compatibility with ksh93.  As of this writing, several ksh93
+features are still missing.
+
+subsect(Ksh Builtins)
+The single builtin provided by this module is:
+
+startitem()
+findex(nameref)
+cindex(named references, creating)
+item(tt(nameref) [ tt(-r) ] var(pname)[tt(=)var(rname)])(
+Equivalent to tt(typeset -n )var(pname)tt(=)var(rname)
+
+However, tt(nameref) is a builtin command rather than a reserved word,
+so when var(rname) uses subscript syntax it must be quoted against
+globbing.  Subscripts in referenced parameters are not supported in
+ksh93, so this is not a significant compatibility issue.
+)
+enditem()
+
+subsect(Ksh Parameters)
+cindex(parameters, ksh)
+Parameters supplied by this module that are marked with `<K>' below are
+available only in ksh emulation.
+
+startitem()
+vindex(.sh.command)
+item(tt(.sh.command))(
+A named reference to `tt(ZSH_DEBUG_CMD)'
+)
+vindex(.sh.edchar)
+item(tt(.sh.edchar) <K>)(
+In a ZLE widget, equivalent to the `tt(KEYS)' special parameter.  In a
+future release, assignments to this parameter will affect the input
+stream seen by ZLE.
+)
+vindex(.sh.edcol)
+item(tt(.sh.edcol))(
+A named reference to the ZLE special parameter `tt(CURSOR)'.
+)
+vindex(.sh.edmode)
+item(tt(.sh.edmode) <K>)(
+In a ZLE widget, this parameter has the value tt(ESC) (tt($'\e')) if the
+`tt(main)' keymap is selected, and the empty string otherwise.  This is
+intended for use with vi-mode key bindings (`tt(bindkey -v)').  In a
+future revision, assigning `tt(.sh.edchar=${.sh.edmode})' is expected
+to initiate `tt(vicmd)' mode when `tt(viins)' is active, and do
+nothing when `tt(vicmd)' is already active.
+)
+vindex(.sh.edtext)
+item(tt(.sh.edtext))(
+A named reference to the `tt(BUFFER)' special ZLE parameter.
+)
+vindex(.sh.file)
+item(tt(.sh.file))(
+A named reference to the `tt(ZSH_SCRIPT)' parameter.
+)
+vindex(.sh.fun)
+item(tt(.sh.fun) <K>)(
+In a shell function, the function's name.  Usually the same as `tt($0)',
+but not affected by tt(POSIX_ARGZERO) or tt(FUNCTION_ARGZERO) options.
+)
+vindex(.sh.level)
+item(tt(.sh.level) <K>)(
+In a shell function, initially set to the depth of the call stack. This
+may be assigned to change the apparent depth.  However, such assignment
+does not affect the scope of local parameters.
+)
+vindex(.sh.lineno)
+item(tt(.sh.lineno))(
+A named reference to the `tt(LINENO)' special parameter.
+)
+vindex(.sh.match)
+item(tt(.sh.match))(
+An array equivalent to the `tt(match)' parameter as assigned by the
+`tt(LPAR()#b)tt(RPAR())' extended globbing flag.  When the
+tt(KSH_ARRAYS) option is set, `tt(${.sh.match[0]})' gives the value of
+the `tt(MATCH)' parameter as set by the `tt(LPAR()#m)tt(RPAR())' extended
+globbing flag.  Currently, the tt(EXTENDED_GLOB) option must be enabled
+and those flags must be explicitly used in a pattern in order for these
+values of `tt(.sh.match)' to be set.
+)
+vindex(.sh.name)
+item(tt(.sh.name) <K>)(
+When the `tt(vared)' command is used, this parameter may be used in
+user-defined ZLE widgets to get the name of the variable being edited.
+A future release is expected to use this parameter in the implementation
+of "discipline functions".
+)
+vindex(.sh.subscript)
+item(tt(.sh.subscript) <K>)(
+When `tt(vared)' has been used on an array element, this parameter holds
+the array index (either a number, or an associative array key).
+)
+vindex(.sh.subshell)
+item(tt(.sh.subshell))(
+A named reference to the `tt(ZSH_SUBSHELL)' parameter.
+)
+vindex(.sh.value)
+item(tt(.sh.value) <K>)(
+In `tt(vared)' this is a named reference to the ZLE special `tt(BUFFER)'.
+A future release is expected to use this parameter in the implementation
+of "discipline functions".
+)
+vindex(.sh.version)
+item(tt(.sh.version))(
+A named reference to `tt(ZSH_PATCHLEVEL)'.
+)
+enditem()
+
+subsect(Future Compatibility)
+
+The following features of ksh93 are not currently supported but may be
+available in a future release.
+
+startitem()
+item(var(pathdir)tt(/.paths))(
+Each directory var(pathdir) in the tt(PATH) parameter may contain a
+text file `tt(.paths)' which may define additional directories to
+be searched for function definitions (tt(FPATH)), external executables
+(tt(PATH)), and plugin files (similar to tt(MODULE_PATH)).
+
+em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.)
+)
+item(tt(builtin -f )var(file))(
+Installs a new shell builtin command dynamically linked from var(file),
+where var(file) is found by a path search and the base name of the file
+is the name of the builtin to be added.
+
+Similar to `tt(zmodload -F zsh/)var(file)tt( +b:)var(file)'.
+
+em(THIS FEATURE IS UNLIKELY EVER TO BE IMPLEMENTED.)
+)
+item(tt(namespace )var(ident)tt( { )var(list)tt( }))(
+This reserved word executes the current shell compound command
+tt({ )var(list)tt( }), with the special behavior that all functions
+and parameters `var(name)' declared within var(list) are implicitly
+prefixed to become `tt(.)var(ident)tt(.)var(name)', and similarly any
+reference to a function or parameter `var(name)' is searched for as
+`tt(.)var(ident)tt(.)var(name)' before falling back to `var(name)'.
+
+em(THIS FEATURE IS NOT YET IMPLEMENTED.)
+)
+item(tt(.sh.math) <K>)(
+This parameter is more accurately considered a namespace.  A function
+defintion of the form
+ifzman()
+indent(tt(function .sh.math.)var(name)tt( )var(ident)tt( ... { )var(list)tt( }))
+
+is equivalent to the native zsh definition
+ifzman()
+example(tt(function )var(name)tt( {)
+tt(  local )var(ident)tt(=$1 ...)
+tt(  )var(list)
+tt(})
+tt(functions -M )var(name)tt( 1 3))
+ifzman()
+Up to 3 var(ident) arguments, interpreted as floating point numbers,
+may be provided for a function defined with tt(.sh.math) in this way.
+The names (but not definitions) of all such functions are available
+via tt(${.sh.math[@]}).
+
+em(THIS FEATURE IS NOT YET IMPLEMENTED.)
+)
+item(tt(var(name)tt(.get)))(
+A shell function having this name, if defined, is invoked whenever the
+parameter tt(${)var(name)tt(}) is referenced, including by commands
+such as `tt(typeset -p)'.  If the special variable `tt(.sh.value)' is
+assigned by the function, that value is substituted instead of the
+true value of var(name).  This does not change the value of var(name),
+but there is no way to access the actual value without first removing
+the function.
+
+Additionally, an explicit reference to tt(${)var(name)tt(.get})
+calls the function var(name)tt(.get) even if there is no parameter
+`var(name)' and substitutes the standard output of the function.
+
+em(THIS FEATURE IS NOT YET IMPLEMENTED.)
+)
+xitem(tt(var(name)tt(.set)))
+item(tt(var(name)tt(.append)))(
+Shell functions having these names are invoked when the parameter
+var(name) is assigned or (for array types) has a new field appended.
+The function may change the result of the operation by assigning to
+the `tt(.sh.value)' special parameter, or block the change by
+unsetting `tt(.sh.value)'.
+
+Explicit reference to tt(${)var(name)tt(.set}) or tt(${)var(name)tt(.append})
+substitutes the standard output of the function.
+
+em(THIS FEATURE IS NOT YET IMPLEMENTED.)
+)
+item(tt(var(name)tt(.unset)))(
+When a function of this name is defined, it is called whenever the
+parameter var(name) would be unset.  The function must explicitly
+`tt(unset )var(name)', otherwise the variable remains set.
+
+em(THIS FEATURE IS NOT YET IMPLEMENTED.)
+)
+item(tt(${ )var(list)tt(;}))(
+Note the space after the opening brace (tt({)). This executes var(list)
+in the current shell and substitutes its standard output in the manner
+of `tt($LPAR())var(list)tt(RPAR())' but without forking.
+
+em(THIS FEATURE IS NOT YET IMPLEMENTED.)
+)
+enditem()
diff --git a/Src/Modules/ksh93.c b/Src/Modules/ksh93.c
new file mode 100644
index 000000000..9dc75c93c
--- /dev/null
+++ b/Src/Modules/ksh93.c
@@ -0,0 +1,265 @@
+/*
+ * ksh93.c - support for more ksh93 features
+ *
+ * This file is part of zsh, the Z shell.
+ *
+ * Copyright (c) 2022 Barton E. Schaefer
+ * All rights reserved.
+ *
+ * Permission is hereby granted, without written agreement and without
+ * license or royalty fees, to use, copy, modify, and distribute this
+ * software and to distribute modified versions of this software for any
+ * purpose, provided that the above copyright notice and the following
+ * two paragraphs appear in all copies of this software.
+ *
+ * In no event shall Barton E. Schaefer or the Zsh Development
+ * Group be liable to any party for direct, indirect, special, incidental, or
+ * consequential damages arising out of the use of this software and its
+ * documentation, even if Barton E. Schaefer and the Zsh
+ * Development Group have been advised of the possibility of such damage.
+ *
+ * Barton E. Schaefer and the Zsh Development Group
+ * specifically disclaim any warranties, including, but not limited to, the
+ * implied warranties of merchantability and fitness for a particular purpose.
+ * The software provided hereunder is on an "as is" basis, and
+ * Barton E. Schaefer and the Zsh Development Group have no
+ * obligation to provide maintenance, support, updates, enhancements, or
+ * modifications.
+ *
+ */
+
+#include "ksh93.mdh"
+#include "ksh93.pro"
+
+/* Implementing "namespace" requires creating a new keword.  Hrm. */
+
+/*
+ * Standard module configuration/linkage
+ */
+
+static struct builtin bintab[] = {
+    BUILTIN("nameref", BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "gr", "n")
+};
+
+#include "zsh.mdh"
+
+static void
+edcharsetfn(Param pm, char *x)
+{
+    /*
+     * To make this work like ksh, we must intercept $KEYS before the widget
+     * is looked up, so that changing the key sequence causes a different
+     * widget to be substituted.  Somewhat similar to "bindkey -s".
+     *
+     * Ksh93 adds SIGKEYBD to the trap list for this purpose.
+     */
+    ;
+}
+
+static char **
+matchgetfn(Param pm)
+{
+    char **zsh_match = getaparam("match");
+
+    /* For this to work accurately, ksh emulation should always imply
+     * that the (#m) and (#b) extendedglob operators are enabled.
+     *
+     * When we have a 0th element (ksharrays), it is $MATCH.  Elements
+     * 1st and larger mirror the $match array.
+     */
+
+    if (pm->u.arr)
+	freearray(pm->u.arr);
+    if (zsh_match && *zsh_match) {
+	if (isset(KSHARRAYS)) {
+	    char **ap =
+		(char **) zalloc(sizeof(char *) * (arrlen(zsh_match)+1));
+	    pm->u.arr = ap;
+	    *ap++ = ztrdup(getsparam("MATCH"));
+	    while (*zsh_match)
+		*ap = ztrdup(*zsh_match++);
+	} else
+	    pm->u.arr = zarrdup(zsh_match);
+    } else if (isset(KSHARRAYS)) {
+	pm->u.arr = mkarray(ztrdup(getsparam("MATCH")));
+    } else
+	pm->u.arr = NULL;
+
+    return arrgetfn(pm);
+}
+
+static const struct gsu_scalar constant_gsu =
+    { strgetfn, NULL, nullunsetfn };
+
+static const struct gsu_scalar sh_edchar_gsu =
+    { strvargetfn, edcharsetfn, nullunsetfn };
+static const struct gsu_scalar sh_edmode_gsu =
+    { strgetfn, nullstrsetfn, nullunsetfn };
+static const struct gsu_array sh_match_gsu =
+    { matchgetfn, arrsetfn, stdunsetfn };
+static const struct gsu_scalar sh_name_gsu =
+    { strvargetfn, nullstrsetfn, nullunsetfn };
+static const struct gsu_scalar sh_subscript_gsu =
+    { strvargetfn, nullstrsetfn, nullunsetfn };
+
+static char *sh_name;
+static char *sh_subscript;
+static char *sh_edchar;
+static char sh_edmode[2];
+
+/*
+ * Some parameters listed here do not appear in ksh93.mdd autofeatures
+ * because they are only instantiated by ksh93_wrapper() below.  This
+ * obviously includes those commented out here.
+ */
+static struct paramdef partab[] = {
+    PARAMDEF(".sh.command", PM_NAMEREF|PM_READONLY, "ZSH_DEBUG_CMD", &constant_gsu),
+    PARAMDEF(".sh.edchar", PM_SCALAR|PM_SPECIAL, &sh_edchar, &sh_edchar_gsu),
+    PARAMDEF(".sh.edcol", PM_NAMEREF|PM_READONLY, "CURSOR", &constant_gsu),
+    PARAMDEF(".sh.edmode", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_edmode, &sh_edmode_gsu),
+    PARAMDEF(".sh.edtext", PM_NAMEREF|PM_READONLY, "BUFFER", &constant_gsu),
+    PARAMDEF(".sh.file", PM_NAMEREF|PM_READONLY, "ZSH_SCRIPT", &constant_gsu),
+    /* PARAMDEF(".sh.fun", PM_SCALAR|PM_UNSET, NULL, &constant_gsu), */
+    /* PARAMDEF(".sh.level", PM_INTEGER|PM_UNSET, NULL, &constant_gsu), */
+    PARAMDEF(".sh.lineno", PM_NAMEREF|PM_READONLY, "LINENO", &constant_gsu),
+    PARAMDEF(".sh.match", PM_ARRAY|PM_READONLY, NULL, &sh_match_gsu),
+    PARAMDEF(".sh.name", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_name, &sh_name_gsu),
+    PARAMDEF(".sh.subscript", PM_SCALAR|PM_READONLY|PM_SPECIAL, &sh_subscript, &sh_subscript_gsu),
+    PARAMDEF(".sh.subshell", PM_NAMEREF|PM_READONLY, "ZSH_SUBSHELL", &constant_gsu),
+    /* SPECIALPMDEF(".sh.value", 0, NULL, NULL, NULL), */
+    PARAMDEF(".sh.version", PM_NAMEREF|PM_READONLY, "ZSH_PATCHLEVEL", &constant_gsu)
+};
+
+static struct features module_features = {
+    bintab, sizeof(bintab)/sizeof(*bintab),
+    NULL, 0,
+    NULL, 0,
+    partab, sizeof(partab)/sizeof(*partab),
+    0
+};
+
+/**/
+static int
+ksh93_wrapper(Eprog prog, FuncWrap w, char *name)
+{
+    Funcstack f;
+    Param pm;
+    zlong num = funcstack->prev ? getiparam(".sh.level") : 0;
+
+    if (!EMULATION(EMULATE_KSH))
+	return 1;
+
+    if (num == 0)
+	for (f = funcstack; f; f = f->prev, num++);
+    else
+	num++;
+
+    queue_signals();
+    ++locallevel;		/* Make these local */
+    if ((pm = createparam(".sh.level", PM_LOCAL|PM_UNSET))) {
+	pm->level = locallevel;	/* Why is this necessary? */
+	setiparam(".sh.level", num);
+    }
+    if ((pm = createparam(".sh.fun", PM_LOCAL|PM_UNSET))) {
+	pm->level = locallevel;
+	setsparam(".sh.fun", ztrdup(name));
+	pm->node.flags |= PM_READONLY;
+    }
+    if (zleactive) {
+	extern mod_import_variable char *curkeymapname;	/* XXX */
+	extern mod_import_variable char *varedarg;	/* XXX */
+	/* How to distinguish emacs bindings? */
+	if (curkeymapname && strcmp(curkeymapname, "main") == 0)
+	    strcpy(sh_edmode, "\e");
+	else
+	    strcpy(sh_edmode, "");
+	if (!sh_edchar)
+	    sh_edchar = dupstring(getsparam("KEYS"));
+	if (varedarg) {
+	    char *ie = itype_end((sh_name = dupstring(varedarg)), INAMESPC, 0);
+	    if (ie && *ie) {
+		*ie++ = '\0';
+		/* Assume bin_vared has validated subscript */
+		sh_subscript = dupstring(ie);
+		ie = sh_subscript + strlen(sh_subscript);
+		*--ie = '\0';
+	    } else
+		sh_subscript = NULL;
+	    if ((pm = createparam(".sh.value", PM_LOCAL|PM_NAMEREF|PM_UNSET))) {
+		pm->level = locallevel;
+		setloopvar(".sh.value", "BUFFER");	/* Hack */
+	    }
+	} else
+	    sh_name = sh_subscript = NULL;
+    } else {
+	sh_edchar = sh_name = sh_subscript = NULL;
+	strcpy(sh_edmode, "");
+	/* TODO:
+	 * - disciplines
+	 * - special handling of .sh.value in math
+	 */
+    }
+    --locallevel;
+    unqueue_signals();
+
+    return 1;
+}
+
+static struct funcwrap wrapper[] = {
+    WRAPDEF(ksh93_wrapper),
+};
+
+/**/
+int
+setup_(UNUSED(Module m))
+{
+    return 0;
+}
+
+/**/
+int
+features_(Module m, char ***features)
+{
+    *features = featuresarray(m, &module_features);
+    return 0;
+}
+
+/**/
+int
+enables_(Module m, int **enables)
+{
+    return handlefeatures(m, &module_features, enables);
+}
+
+/**/
+int
+boot_(Module m)
+{
+    return addwrapper(m, wrapper);
+}
+
+/**/
+int
+cleanup_(Module m)
+{
+    struct paramdef *p;
+
+    deletewrapper(m, wrapper);
+
+    /* Clean up namerefs, otherwise deleteparamdef() is confused */
+    for (p = partab; p < partab + sizeof(partab)/sizeof(*partab); ++p) {
+	if (p->flags & PM_NAMEREF) {
+	    HashNode hn = gethashnode2(paramtab, p->name);
+	    if (hn)
+		((Param)hn)->node.flags &= ~PM_NAMEREF;
+	}
+    }
+    return setfeatureenables(m, &module_features, NULL);
+}
+
+/**/
+int
+finish_(UNUSED(Module m))
+{
+    return 0;
+}
diff --git a/Src/Modules/ksh93.mdd b/Src/Modules/ksh93.mdd
new file mode 100644
index 000000000..2759884a0
--- /dev/null
+++ b/Src/Modules/ksh93.mdd
@@ -0,0 +1,8 @@
+name=zsh/ksh93
+link=either
+load=yes
+
+autofeatures="b:nameref"
+autofeatures_emu="b:nameref p:.sh.command p:.sh.edcol p:.sh.edtext p:.sh.file p:.sh.lineno p:.sh.match p:.sh.subshell p:.sh.version"
+
+objects="ksh93.o"
diff --git a/Src/utils.c b/Src/utils.c
index 1393ecb13..8ce9a175d 100644
--- a/Src/utils.c
+++ b/Src/utils.c
@@ -4319,7 +4319,7 @@ itype_end(const char *ptr, int itype, int once)
 {
     if (itype == INAMESPC) {
 	itype = IIDENT;
-	if (once == 0 && !isset(POSIXIDENTIFIERS)) {
+	if (once == 0 && (!isset(POSIXIDENTIFIERS) || EMULATION(EMULATE_KSH))) {
 	    /* Special case for names containing ".", ksh93 namespaces */
 	    char *t = itype_end(ptr + (*ptr == '.'), itype, 0);
 	    if (t > ptr+1) {



Messages sorted by: Reverse Date, Date, Thread, Author