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 `' 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) )( +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) )( +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) )( +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) )( +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) )( +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) )( +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) )( +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) )( +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) {