51485: module for several ksh93 features, mostly enabled only in ksh emulation.

This commit is contained in:
Bart Schaefer 2023-03-05 14:16:31 -08:00
parent 4bc1f6e0d2
commit ea0bd72dd8
6 changed files with 490 additions and 2 deletions

View file

@ -1,5 +1,9 @@
2023-03-05 Bart Schaefer <schaefer@zsh.org>
* 51485: Doc/Makefile.in, Doc/Zsh/mod_ksh93.yo, Src/utils.c,
Src/Modules/ksh93.c, Src/Modules/ksh93.mdd: module for several
ksh93 features, mostly enabled only in ksh emulation.
* 51484: Src/builtins.yo Src/params.c: Extend named reference
handling for special parameters, improve doc.

View file

@ -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 \

211
Doc/Zsh/mod_ksh93.yo Normal file
View file

@ -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()

265
Src/Modules/ksh93.c Normal file
View file

@ -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;
}

8
Src/Modules/ksh93.mdd Normal file
View file

@ -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"

View file

@ -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) {