1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2024-05-13 11:06:17 +02:00

various posts: Implement assignment parsing for typeset.

Typeset assignments now work like raw assignments except
for no "+=" and no GLOB_ASSIGN.

Documented in typeset builtin doc and mentioned in release notes.

Tests to ensure basic sanity.

Enabled by default, can be turned off by "disable -r" with typeset
family of commands.
This commit is contained in:
Peter Stephenson 2015-06-18 14:54:41 +01:00
parent a68d22eb00
commit 39b28980f3
18 changed files with 893 additions and 191 deletions

View File

@ -1,3 +1,14 @@
2015-06-24 Peter Stephenson <p.stephenson@samsung.com>
* various culminating in 35586, c.f. commits on typeset-array
branch: Config/version.mk, Doc/Zsh/builtins.yo,
Doc/Zsh/grammar.yo, Doc/Zsh/options.yo, NEWS, README,
Src/builtin.c, Src/exec.c, Src/hashtable.c, Src/lex.c,
Src/options.c, Src/parse.c, Src/text.c, Src/zsh.h,
Test/B02typeset.ztst, Test/D01prompt.ztst, Test/E01options.ztst:
Implement assignment handling for typeset etc. when matched as
reserved words. Document and test.
2015-06-23 Peter Stephenson <p.stephenson@samsung.com>
* 35573: Completion/compinit: turn off POSIX_BUILTINS

View File

@ -27,5 +27,5 @@
# This must also serve as a shell script, so do not add spaces around the
# `=' signs.
VERSION=5.0.8-dev-0
VERSION_DATE='June 1, 2015'
VERSION=5.0.8-dev-1
VERSION_DATE='June 19, 2015'

View File

@ -1729,7 +1729,7 @@ xitem(tt(typeset )[ {tt(PLUS())|tt(-)}tt(AHUaghlmprtux) ] \
[ {tt(PLUS())|tt(-)}tt(EFLRZi) [ var(n) ] ])
xitem(SPACES()[ tt(+) | var(name)[tt(=)var(value)] ... ])
xitem(tt(typeset )tt(-T) [ {tt(PLUS())|tt(-)}tt(Uglprux) ] [ {tt(PLUS())|tt(-)}tt(LRZ) [ var(n) ] ])
xitem(SPACES()[ tt(+) | var(SCALAR)[tt(=)var(value)] var(array) [ var(sep) ] ])
xitem(SPACES()[ tt(+) | var(SCALAR)[tt(=)var(value)] var(array)[tt(=)LPAR()var(value)RPAR()] [ var(sep) ] ])
item(tt(typeset) tt(-f) [ {tt(PLUS())|tt(-)}tt(TUkmtuz) ] [ tt(+) | var(name) ... ])(
Set or display attributes and values for shell parameters.
@ -1743,22 +1743,72 @@ ifnzman(noderef(Local Parameters))\
retain their special attributes when made local.
For each var(name)tt(=)var(value) assignment, the parameter
var(name) is set to var(value). Note that arrays currently cannot be
assigned in tt(typeset) expressions, only scalars and integers. Unless
the option tt(KSH_TYPESET) is set, normal expansion rules apply to
assignment arguments, so var(value) may be split into separate words; if
the option is set, assignments which can be recognised when expansion is
performed are treated as single words. For example the command
tt(typeset vbl=$(echo one two)) is treated as having one argument if
tt(KSH_TYPESET) is set, but otherwise is treated as having the two arguments
tt(vbl=one) and tt(two).
var(name) is set to var(value). All forms of the command
handle scalar assignment.
If any of the reserved words tt(declare), tt(export), tt(float),
tt(integer), tt(local), tt(readonly) or tt(typeset) is matched when the
line is parsed (N.B. not when it is executed) the shell will try to parse
arguments as assignments, except that the `tt(+=)' syntax and the
tt(GLOB_ASSIGN) option are not supported. This has two major differences
from normal command line argument parsing: array assignment is possible,
and scalar values after tt(=) are not split further into words even if
expanded (regardless of the setting of the tt(KSH_TYPESET) option; this
option is obsolete). Here is an example:
example(# Reserved word parsing
typeset svar=$(echo one word) avar=(several words))
The above creates a scalar parameter tt(svar) and an array
parameter tt(var) as if the assignments had been
example(svar="one word"
avar=(several words))
On the other hand:
example(# Normal builtin interface
builtin typeset svar=$(echo two words))
The tt(builtin) keyword causes the above to use the standard builtin
interface to tt(typeset) in which argument parsing is perfomed in the same
way as for other commands. This example creates a scalar tt(svar)
containing the value tt(two) and another scalar parameter tt(words) with
no value. An array value in this case would either cause an error or be
treated as an obscure set of glob qualifiers.
Arbitrary arguments are allowed if they take the form of assignments
after command line expansion; however, these only perform scalar
assignment:
example(var='svar=val'
typeset $var)
The above sets the scalar parameter tt(svar) to the value tt(val).
Parentheses around the value within tt(var) would not cause array
assignment as they will be treated as ordinary characters when tt($var)
is substituted. Any non-trivial expansion in the name part of the
assignment causes the argument to be treated in this fashion:
example(typeset {var1,var2,var3}=name)
The above syntax is valid, and has the expected effect of setting the
three parameters to the same value, but the command line is parsed as
a set of three normal command line arguments to tt(typeset) after
expansion. Hence it is not possible to assign to multiple arrays by
this means.
Note that each interface to any of the commands my be disabled
separately. For example, `tt(disable -r typeset)' disables the reserved
word interface to tt(typeset), exposing the builtin interface, while
`tt(disable typeset)' disables the builtin.
If the shell option tt(TYPESET_SILENT) is not set, for each remaining
var(name) that refers to a parameter that is set, the name and value of the
parameter are printed in the form of an assignment. Nothing is printed for
newly-created parameters, or when any attribute flags listed below are
given along with the var(name). Using `tt(PLUS())' instead of minus to
introduce an attribute turns it off.
var(name) that refers to a parameter that is already set, the name and
value of the parameter are printed in the form of an assignment.
Nothing is printed for newly-created parameters, or when any attribute
flags listed below are given along with the var(name). Using
`tt(PLUS())' instead of minus to introduce an attribute turns it off.
If no var(name) is present, the names and values of all parameters are
printed. In this case the attribute flags restrict the display to only
@ -1829,7 +1879,7 @@ the current state, readonly specials (whose values cannot be
changed) are not shown and assignments to arrays are shown before
the tt(typeset) rendering the array readonly.
)
item(tt(-T) [ var(scalar)[tt(=)var(value)] var(array) [ var(sep) ] ])(
item(tt(-T) [ var(scalar)[tt(=)var(value)] var(array)[tt(=)LPAR()var(value)...RPAR()] [ var(sep) ] ])(
This flag has a different meaning when used with tt(-f); see below.
Otherwise the tt(-T) option requires zero, two, or three arguments to be
present. With no arguments, the list of parameters created in this
@ -1839,10 +1889,13 @@ together in the manner of tt($PATH) and tt($path). The optional third
argument is a single-character separator which will be used to join the
elements of the array to form the scalar; if absent, a colon is used, as
with tt($PATH). Only the first character of the separator is significant;
any remaining characters are ignored.
any remaining characters are ignored. Multibyte characters are not
yet supported.
Only the scalar parameter may be assigned an initial value. Both the
scalar and the array may otherwise be manipulated as normal. If one is
Only one of the scalar and array parameters may be assigned an initial
value (the restrictions on assignment forms described above also apply).
Both the scalar and the array may be manipulated as normal. If one is
unset, the other will automatically be unset too. There is no way of
untying the variables without unsetting them, nor of converting the type
of one of them with another tt(typeset) command; tt(+T) does not work,

View File

@ -472,7 +472,8 @@ word of a command unless quoted or disabled using tt(disable -r):
tt(do done esac then elif else fi for case
if while function repeat time until
select coproc nocorrect foreach end ! [[ { })
select coproc nocorrect foreach end ! [[ { }
declare export float integer local readonly typeset)
Additionally, `tt(})' is recognized in any position if neither the
tt(IGNORE_BRACES) option nor the tt(IGNORE_CLOSE_BRACES) option is set.

View File

@ -1928,7 +1928,13 @@ pindex(KSHTYPESET)
pindex(NOKSHTYPESET)
cindex(argument splitting, in typeset etc.)
cindex(ksh, argument splitting in typeset)
item(tt(KSH_TYPESET) <K>)(
item(tt(KSH_TYPESET))(
This option is now obsolete: a better appropximation to the behaviour of
other shells is obtained with the reserved word interface to
tt(declare), tt(export), tt(float), tt(integer), tt(local), tt(readonly)
and tt(typeset). Note that the option is only applied when the reserved
word interface is em(not) in use.
Alters the way arguments to the tt(typeset) family of commands, including
tt(declare), tt(export), tt(float), tt(integer), tt(local) and
tt(readonly), are processed. Without this option, zsh will perform normal

19
NEWS
View File

@ -4,7 +4,21 @@ CHANGES FROM PREVIOUS VERSIONS OF ZSH
Note also the list of incompatibilities in the README file.
Changes from 5.0.7 to 5.0.8
Changes from 5.0.8 to 5.0.9
---------------------------
The builtins declare, export, local, readonly and typeset
now have corresponding reserved words. When used in
this form, the builtin syntax is extended so that assignments
following the reserved word are treated similarly to
assignments that appear at the start of the command line.
For example,
local scalar=`echo one word` array=(several words)
creates a local "scalar" containing the text "one word"
and an array "array" containing the words "several"
"words".
Changes from 5.0.0 to 5.0.8
---------------------------
- Global aliases can be created for syntactic tokens such as command
@ -47,9 +61,6 @@ Changes from 5.0.7 to 5.0.8
- Some rationalisations have been made to the zsh/db/gdbm module that
should make it more useful and predictable in operation.
Changes from 5.0.0 to 5.0.7
---------------------------
- Numeric constants encountered in mathematical expressions (but not other
contexts) can contain underscores as separators that will be ignored on
evaluation, as allowed in other scripting languages. For example,

17
README
View File

@ -30,6 +30,23 @@ Zsh is a shell with lots of features. For a list of some of these, see the
file FEATURES, and for the latest changes see NEWS. For more
details, see the documentation.
Incompatibilites between 5.0.8 and 5.0.9
----------------------------------------
As noted in NEWS, the builtins declare, export, float, integer, local,
readonly and typeset now have corresponding reserved words that provide
true assignment semantics instead of an approximation by means of normal
command line arguments. It is hoped that this additional consistency
provides a more natural interface. However, compatbility with older
versions of zsh can be obtained by turning off the reserved word
interface, exposing the builtin interface:
disable -r declare export float integer local readonly typeset
This is also necessary in the unusual eventuality that the builtins are
to be overridden by shell functions, since reserved words take
precedence over functions.
Incompatibilites between 5.0.7 and 5.0.8
----------------------------------------

View File

@ -53,7 +53,7 @@ static struct builtin builtins[] =
BUILTIN("cd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
BUILTIN("chdir", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_CD, "qsPL", NULL),
BUILTIN("continue", BINF_PSPECIAL, bin_break, 0, 1, BIN_CONTINUE, NULL, NULL),
BUILTIN("declare", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klmprtuxz", NULL),
BUILTIN("declare", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klmprtuxz", NULL),
BUILTIN("dirs", 0, bin_dirs, 0, -1, 0, "clpv", NULL),
BUILTIN("disable", 0, bin_enable, 0, -1, BIN_DISABLE, "afmprs", NULL),
BUILTIN("disown", 0, bin_fg, 0, -1, BIN_DISOWN, NULL, NULL),
@ -62,7 +62,7 @@ static struct builtin builtins[] =
BUILTIN("enable", 0, bin_enable, 0, -1, BIN_ENABLE, "afmprs", NULL),
BUILTIN("eval", BINF_PSPECIAL, bin_eval, 0, -1, BIN_EVAL, NULL, NULL),
BUILTIN("exit", BINF_PSPECIAL, bin_break, 0, 1, BIN_EXIT, NULL, NULL),
BUILTIN("export", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, BIN_EXPORT, "E:%F:%HL:%R:%TUZ:%afhi:%lprtu", "xg"),
BUILTIN("export", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, BIN_EXPORT, "E:%F:%HL:%R:%TUZ:%afhi:%lprtu", "xg"),
BUILTIN("false", 0, bin_false, 0, -1, 0, NULL, NULL),
/*
* We used to behave as if the argument to -e was optional.
@ -71,7 +71,7 @@ static struct builtin builtins[] =
*/
BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "aAdDe:EfiIlLmnpPrRt:W", NULL),
BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlprtux", "E"),
BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlprtux", "E"),
BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "kmMtTuUx:z", NULL),
BUILTIN("getln", 0, bin_read, 0, -1, 0, "ecnAlE", "zr"),
BUILTIN("getopts", 0, bin_getopts, 2, -1, 0, NULL, NULL),
@ -82,11 +82,11 @@ static struct builtin builtins[] =
#endif
BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "adDEfiLmnpPrt:", "l"),
BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "HL:%R:%Z:%ghi:%lprtux", "i"),
BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "HL:%R:%Z:%ghi:%lprtux", "i"),
BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL),
BUILTIN("kill", BINF_HANDLES_OPTS, bin_kill, 0, -1, 0, NULL, NULL),
BUILTIN("let", 0, bin_let, 1, -1, 0, NULL, NULL),
BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%ahi:%lprtux", NULL),
BUILTIN("local", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%ahi:%lprtux", NULL),
BUILTIN("log", 0, bin_log, 0, 0, 0, NULL, NULL),
BUILTIN("logout", 0, bin_break, 0, 1, BIN_LOGOUT, NULL, NULL),
@ -106,7 +106,7 @@ static struct builtin builtins[] =
BUILTIN("pwd", 0, bin_pwd, 0, 0, 0, "rLP", NULL),
BUILTIN("r", 0, bin_fc, 0, -1, BIN_R, "IlLnr", NULL),
BUILTIN("read", 0, bin_read, 0, -1, 0, "cd:ek:%lnpqrst:%zu:AE", NULL),
BUILTIN("readonly", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%lptux", "r"),
BUILTIN("readonly", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%lptux", "r"),
BUILTIN("rehash", 0, bin_hash, 0, 0, 0, "df", "r"),
BUILTIN("return", BINF_PSPECIAL, bin_break, 0, 1, BIN_RETURN, NULL, NULL),
BUILTIN("set", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_set, 0, -1, 0, NULL, NULL),
@ -120,7 +120,7 @@ static struct builtin builtins[] =
BUILTIN("trap", BINF_PSPECIAL | BINF_HANDLES_OPTS, bin_trap, 0, -1, 0, NULL, NULL),
BUILTIN("true", 0, bin_true, 0, -1, 0, NULL, NULL),
BUILTIN("type", 0, bin_whence, 0, -1, 0, "ampfsSw", "v"),
BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL, bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klprtuxmz", NULL),
BUILTIN("typeset", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "AE:%F:%HL:%R:%TUZ:%afghi:%klprtuxmz", NULL),
BUILTIN("umask", 0, bin_umask, 0, 1, 0, "S", NULL),
BUILTIN("unalias", 0, bin_unhash, 0, -1, BIN_UNALIAS, "ams", NULL),
BUILTIN("unfunction", 0, bin_unhash, 1, -1, BIN_UNFUNCTION, "m", "f"),
@ -246,7 +246,7 @@ new_optarg(Options ops)
/**/
int
execbuiltin(LinkList args, Builtin bn)
execbuiltin(LinkList args, LinkList assigns, Builtin bn)
{
char *pp, *name, *optstr;
int flags, sense, argc, execop, xtr = isset(XTRACE);
@ -443,11 +443,44 @@ execbuiltin(LinkList args, Builtin bn)
fputc(' ', xtrerr);
quotedzputs(*fullargv++, xtrerr);
}
if (assigns) {
LinkNode node;
for (node = firstnode(assigns); node; incnode(node)) {
Asgment asg = (Asgment)node;
fputc(' ', xtrerr);
quotedzputs(asg->name, xtrerr);
if (asg->is_array) {
LinkNode arrnode;
fprintf(xtrerr, "=(");
for (arrnode = firstnode(asg->value.array);
arrnode;
incnode(arrnode)) {
fputc(' ', xtrerr);
quotedzputs((char *)getdata(arrnode), xtrerr);
}
fprintf(xtrerr, " )");
} else if (asg->value.scalar) {
fputc('=', xtrerr);
quotedzputs(asg->value.scalar, xtrerr);
}
}
}
fputc('\n', xtrerr);
fflush(xtrerr);
}
/* call the handler function, and return its return value */
return (*(bn->handlerfunc)) (name, argv, &ops, bn->funcid);
if (flags & BINF_ASSIGN)
{
/*
* Takes two sets of arguments.
*/
HandlerFuncAssign assignfunc = (HandlerFuncAssign)bn->handlerfunc;
return (*(assignfunc)) (name, argv, assigns, &ops, bn->funcid);
}
else
{
return (*(bn->handlerfunc)) (name, argv, &ops, bn->funcid);
}
}
}
@ -1452,12 +1485,13 @@ bin_fc(char *nam, char **argv, Options ops, int func)
if (!asgf)
asgf = asgl = a;
else {
asgl->next = a;
asgl->node.next = &a->node;
asgl = a;
}
a->name = *argv;
a->value = s;
a->next = NULL;
a->is_array = 0;
a->value.scalar = s;
a->node.next = a->node.prev = NULL;
argv++;
}
/* interpret and check first history line specifier */
@ -1631,8 +1665,8 @@ fcsubs(char **sp, struct asgment *sub)
/* loop through the linked list */
while (sub) {
oldstr = sub->name;
newstr = sub->value;
sub = sub->next;
newstr = sub->value.scalar;
sub = (Asgment)sub->node.next;
oldpos = s;
/* loop over occurences of oldstr in s, replacing them with newstr */
while ((newpos = (char *)strstr(oldpos, oldstr))) {
@ -1820,13 +1854,22 @@ fcedit(char *ename, char *fn)
/**/
static Asgment
getasg(char *s)
getasg(char ***argvp, LinkList assigns)
{
char *s = **argvp;
static struct asgment asg;
/* sanity check for valid argument */
if (!s)
if (!s) {
if (assigns) {
Asgment asgp = (Asgment)firstnode(assigns);
if (!asgp)
return NULL;
(void)uremnode(assigns, &asgp->node);
return asgp;
}
return NULL;
}
/* check if name is empty */
if (*s == '=') {
@ -1834,6 +1877,7 @@ getasg(char *s)
return NULL;
}
asg.name = s;
asg.is_array = 0;
/* search for `=' */
for (; *s && *s != '='; s++);
@ -1841,11 +1885,12 @@ getasg(char *s)
/* found `=', so return with a value */
if (*s) {
*s = '\0';
asg.value = s + 1;
asg.value.scalar = s + 1;
} else {
/* didn't find `=', so we only have a name */
asg.value = NULL;
asg.value.scalar = NULL;
}
(*argvp)++;
return &asg;
}
@ -1927,7 +1972,7 @@ typeset_setwidth(const char * name, Param pm, Options ops, int on, int always)
/**/
static Param
typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
int on, int off, int roff, char *value, Param altpm,
int on, int off, int roff, Asgment asg, Param altpm,
Options ops, int joinchar)
{
int usepm, tc, keeplocal = 0, newspecial = NS_NONE, readonly;
@ -1975,7 +2020,24 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
/* attempting a type conversion, or making a tied colonarray? */
tc = 0;
if (usepm || newspecial != NS_NONE) {
if (ASG_ARRAYP(asg) && PM_TYPE(on) == PM_SCALAR &&
!(usepm && (PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED))))
on |= PM_ARRAY;
if (usepm && ASG_ARRAYP(asg) && newspecial == NS_NONE &&
PM_TYPE(pm->node.flags) != PM_ARRAY &&
PM_TYPE(pm->node.flags) != PM_HASHED) {
if (on & (PM_EFLOAT|PM_FFLOAT|PM_INTEGER)) {
zerrnam(cname, "%s: can't assign array value to non-array", pname);
return NULL;
}
if (pm->node.flags & PM_SPECIAL) {
zerrnam(cname, "%s: can't assign array value to non-array special", pname);
return NULL;
}
tc = 1;
usepm = 0;
}
else if (usepm || newspecial != NS_NONE) {
int chflags = ((off & pm->node.flags) | (on & ~pm->node.flags)) &
(PM_INTEGER|PM_EFLOAT|PM_FFLOAT|PM_HASHED|
PM_ARRAY|PM_TIED|PM_AUTOLOAD);
@ -2023,7 +2085,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
tc = 0; /* but don't do a normal conversion */
}
} else if (!setsecondstype(pm, on, off)) {
if (value && !(pm = setsparam(pname, ztrdup(value))))
if (asg->value.scalar && !(pm = setsparam(pname, ztrdup(asg->value.scalar))))
return NULL;
usepm = 1;
err = 0;
@ -2049,7 +2111,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
* Stricter rules about retaining readonly attribute in this case.
*/
if ((on & PM_READONLY) && (!usepm || (pm->node.flags & PM_UNSET)) &&
!value)
!ASG_VALUEP(asg))
on |= PM_UNSET;
else if (usepm && (pm->node.flags & PM_READONLY) &&
!(on & PM_READONLY)) {
@ -2068,8 +2130,14 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
* ii. we are creating a new local parameter
*/
if (usepm) {
if (asg->is_array ?
(asg->value.array && !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED))) :
(asg->value.scalar && (PM_TYPE(pm->node.flags & (PM_ARRAY|PM_HASHED))))) {
zerrnam(cname, "%s: inconsistent type for assignment", pname);
return NULL;
}
on &= ~PM_LOCAL;
if (!on && !roff && !value) {
if (!on && !roff && !ASG_VALUEP(asg)) {
if (OPT_ISSET(ops,'p'))
paramtab->printnode(&pm->node, PRINT_TYPESET);
else if (!OPT_ISSET(ops,'g') &&
@ -2123,15 +2191,17 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
}
if (!(pm->node.flags & (PM_ARRAY|PM_HASHED))) {
if (pm->node.flags & PM_EXPORTED) {
if (!(pm->node.flags & PM_UNSET) && !pm->env && !value)
if (!(pm->node.flags & PM_UNSET) && !pm->env && !ASG_VALUEP(asg))
addenv(pm, getsparam(pname));
} else if (pm->env && !(pm->node.flags & PM_HASHELEM))
delenv(pm);
if (value && !(pm = setsparam(pname, ztrdup(value))))
DPUTS(ASG_ARRAYP(asg), "BUG: typeset got array value where scalar expected");
if (asg->value.scalar && !(pm = setsparam(pname, ztrdup(asg->value.scalar))))
return NULL;
} else if (asg->value.array) {
DPUTS(!ASG_ARRAYP(asg), "BUG: typeset got scalar value where array expected");
if (!(pm = setaparam(pname, zlinklist2array(asg->value.array))))
return NULL;
} else if (value) {
zwarnnam(cname, "can't assign new value for array %s", pname);
return NULL;
}
pm->node.flags |= (on & PM_READONLY);
if (OPT_ISSET(ops,'p'))
@ -2139,6 +2209,13 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
return pm;
}
if (asg->is_array ?
(asg->value.array && !(on & (PM_ARRAY|PM_HASHED))) :
(asg->value.scalar && (on & (PM_ARRAY|PM_HASHED)))) {
zerrnam(cname, "%s: inconsistent type for assignment", pname);
return NULL;
}
/*
* We're here either because we're creating a new parameter,
* or we're adding a parameter at a different local level,
@ -2158,9 +2235,14 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
/*
* Try to carry over a value, but not when changing from,
* to, or between non-scalar types.
*
* (We can do better now, but it does have user-visible
* implications.)
*/
if (!value && !((pm->node.flags|on) & (PM_ARRAY|PM_HASHED)))
value = dupstring(getsparam(pname));
if (!ASG_VALUEP(asg) && !((pm->node.flags|on) & (PM_ARRAY|PM_HASHED))) {
asg->value.scalar = dupstring(getsparam(pname));
asg->is_array = 0;
}
/* pname may point to pm->nam which is about to disappear */
pname = dupstring(pname);
unsetparam_pm(pm, 0, 1);
@ -2251,16 +2333,17 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
return NULL;
}
}
if (PM_TYPE(on) == PM_SCALAR) {
if (PM_TYPE(on) == PM_SCALAR && !ASG_ARRAYP(asg)) {
/*
* This will either complain about bad identifiers, or will set
* a hash element or array slice. This once worked by accident,
* creating a stray parameter along the way via createparam(),
* now called below in the isident() branch.
*/
if (!(pm = setsparam(pname, ztrdup(value ? value : ""))))
if (!(pm = setsparam(pname, ztrdup(asg->value.scalar ? asg->value.scalar : ""))))
return NULL;
value = NULL;
asg->value.scalar = NULL;
asg->is_array = 0;
keeplocal = 0;
on = pm->node.flags;
} else {
@ -2331,10 +2414,17 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
pm->level = keeplocal;
else if (on & PM_LOCAL)
pm->level = locallevel;
if (value && !(pm->node.flags & (PM_ARRAY|PM_HASHED))) {
if (ASG_VALUEP(asg)) {
Param ipm = pm;
if (!(pm = setsparam(pname, ztrdup(value))))
return NULL;
if (pm->node.flags & (PM_ARRAY|PM_HASHED)) {
DPUTS(!ASG_ARRAYP(asg), "BUG: inconsistent scalar value for array");
if (!(pm=setaparam(pname, zlinklist2array(asg->value.array))))
return NULL;
} else {
DPUTS(ASG_ARRAYP(asg), "BUG: inconsistent array value for scalar");
if (!(pm = setsparam(pname, ztrdup(asg->value.scalar))))
return NULL;
}
if (pm != ipm) {
DPUTS(ipm->node.flags != pm->node.flags,
"BUG: parameter recreated with wrong flags");
@ -2371,12 +2461,6 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
}
}
pm->node.flags |= (on & PM_READONLY);
if (value && (pm->node.flags & (PM_ARRAY|PM_HASHED))) {
zerrnam(cname, "%s: can't assign initial value for array", pname);
/* the only safe thing to do here seems to be unset the param */
unsetparam_pm(pm, 0, 1);
return NULL;
}
if (OPT_ISSET(ops,'p'))
paramtab->printnode(&pm->node, PRINT_TYPESET);
@ -2384,11 +2468,18 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func),
return pm;
}
/* declare, export, integer, local, readonly, typeset */
/*
* declare, export, float, integer, local, readonly, typeset
*
* Note the difference in interface from most builtins, covered by the
* BINF_ASSIGN builtin flag. This is only made use of by builtins
* called by reserved word, which only covers declare, local, readonly
* and typeset. Otherwise assigns is NULL.
*/
/**/
int
bin_typeset(char *name, char **argv, Options ops, int func)
bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func)
{
Param pm;
Asgment asg;
@ -2397,6 +2488,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
int on = 0, off = 0, roff, bit = PM_ARRAY;
int i;
int returnval = 0, printflags = 0;
int hasargs;
/* hash -f is really the builtin `functions' */
if (OPT_ISSET(ops,'f'))
@ -2449,7 +2541,8 @@ bin_typeset(char *name, char **argv, Options ops, int func)
/* Given no arguments, list whatever the options specify. */
if (OPT_ISSET(ops,'p'))
printflags |= PRINT_TYPESET;
if (!*argv) {
hasargs = *argv != NULL || (assigns && firstnode(assigns));
if (!hasargs) {
if (!OPT_ISSET(ops,'p')) {
if (!(on|roff))
printflags |= PRINT_TYPE;
@ -2468,9 +2561,9 @@ bin_typeset(char *name, char **argv, Options ops, int func)
if (on & PM_TIED) {
Param apm;
struct asgment asg0;
char *oldval = NULL;
int joinchar;
struct asgment asg0, asg2;
char *oldval = NULL, *joinstr;
int joinchar, nargs;
if (OPT_ISSET(ops,'m')) {
zwarnnam(name, "incompatible options for -T");
@ -2478,34 +2571,41 @@ bin_typeset(char *name, char **argv, Options ops, int func)
return 1;
}
on &= ~off;
if (!argv[1] || (argv[2] && argv[3])) {
nargs = arrlen(argv) + (assigns ? countlinknodes(assigns) : 0);
if (nargs < 2) {
zwarnnam(name, "-T requires names of scalar and array");
unqueue_signals();
return 1;
}
if (nargs > 3) {
zwarnnam(name, "too many arguments for -T");
unqueue_signals();
return 1;
}
/*
* Third argument, if given, is character used to join
* the elements of the array in the scalar.
*/
if (!argv[2])
joinchar = ':';
else if (!*argv[2])
joinchar = 0;
else if (*argv[2] == Meta)
joinchar = argv[2][1] ^ 32;
else
joinchar = *argv[2];
if (!(asg = getasg(argv[0]))) {
if (!(asg = getasg(&argv, assigns))) {
unqueue_signals();
return 1;
}
asg0 = *asg;
if (!(asg = getasg(argv[1]))) {
if (ASG_ARRAYP(&asg0)) {
unqueue_signals();
zwarnnam(name, "first argument of tie must be scalar: %s",
asg0.name);
return 1;
}
if (!(asg = getasg(&argv, assigns))) {
unqueue_signals();
return 1;
}
if (!ASG_ARRAYP(asg) && asg->value.scalar) {
unqueue_signals();
zwarnnam(name, "second argument of tie must be array: %s",
asg->name);
return 1;
}
if (!strcmp(asg0.name, asg->name)) {
unqueue_signals();
zerrnam(name, "can't tie a variable to itself: %s", asg0.name);
@ -2516,6 +2616,36 @@ bin_typeset(char *name, char **argv, Options ops, int func)
zerrnam(name, "can't tie array elements: %s", asg0.name);
return 1;
}
if (ASG_VALUEP(asg) && ASG_VALUEP(&asg0)) {
unqueue_signals();
zerrnam(name, "only one tied parameter can have value: %s", asg0.name);
return 1;
}
/*
* Third argument, if given, is character used to join
* the elements of the array in the scalar.
*/
if (*argv)
joinstr = *argv;
else if (assigns && firstnode(assigns)) {
Asgment nextasg = (Asgment)firstnode(assigns);
if (ASG_ARRAYP(nextasg) || ASG_VALUEP(nextasg)) {
zwarnnam(name, "third argument of tie must be join character");
unqueue_signals();
return 1;
}
joinstr = nextasg->name;
} else
joinstr = NULL;
if (!joinstr)
joinchar = ':';
else if (!*joinstr)
joinchar = 0;
else if (*joinstr == Meta)
joinchar = joinstr[1] ^ 32;
else
joinchar = *joinstr;
/*
* Keep the old value of the scalar. We need to do this
* here as if it is already tied to the same array it
@ -2537,8 +2667,8 @@ bin_typeset(char *name, char **argv, Options ops, int func)
struct tieddata *tdp = (struct tieddata*)pm->u.data;
/* Update join character */
tdp->joinchar = joinchar;
if (asg0.value)
setsparam(asg0.name, ztrdup(asg0.value));
if (asg0.value.scalar)
setsparam(asg0.name, ztrdup(asg0.value.scalar));
return 0;
} else {
zwarnnam(name, "can't tie already tied scalar: %s",
@ -2546,7 +2676,8 @@ bin_typeset(char *name, char **argv, Options ops, int func)
}
return 1;
}
if (!asg0.value && !(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)))
if (!asg0.value.scalar && !asg->value.array &&
!(PM_TYPE(pm->node.flags) & (PM_ARRAY|PM_HASHED)))
oldval = ztrdup(getsparam(asg0.name));
on |= (pm->node.flags & PM_EXPORTED);
}
@ -2554,12 +2685,18 @@ bin_typeset(char *name, char **argv, Options ops, int func)
* Create the tied array; this is normal except that
* it has the PM_TIED flag set. Do it first because
* we need the address.
*
* Don't attempt to set it yet, it's too early
* to be exported properly.
*/
asg2.name = asg->name;
asg2.is_array = 0;
asg2.value.array = (LinkList)0;
if (!(apm=typeset_single(name, asg->name,
(Param)paramtab->getnode(paramtab,
asg->name),
func, (on | PM_ARRAY) & ~PM_EXPORTED,
off, roff, asg->value, NULL, ops, 0))) {
off, roff, &asg2, NULL, ops, 0))) {
if (oldval)
zsfree(oldval);
unqueue_signals();
@ -2572,7 +2709,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
if (!(pm=typeset_single(name, asg0.name,
(Param)paramtab->getnode(paramtab,
asg0.name),
func, on, off, roff, asg0.value, apm,
func, on, off, roff, &asg0, apm,
ops, joinchar))) {
if (oldval)
zsfree(oldval);
@ -2591,7 +2728,9 @@ bin_typeset(char *name, char **argv, Options ops, int func)
if (apm->ename)
zsfree(apm->ename);
apm->ename = ztrdup(asg0.name);
if (oldval)
if (asg->value.array)
setaparam(asg->name, zlinklist2array(asg->value.array));
else if (oldval)
setsparam(asg0.name, oldval);
unqueue_signals();
@ -2611,18 +2750,18 @@ bin_typeset(char *name, char **argv, Options ops, int func)
printflags |= PRINT_NAMEONLY;
}
while ((asg = getasg(*argv++))) {
while ((asg = getasg(&argv, assigns))) {
LinkList pmlist = newlinklist();
LinkNode pmnode;
tokenize(asg->name); /* expand argument */
if (!(pprog = patcompile(asg->name, 0, NULL))) {
untokenize(asg->name);
zwarnnam(name, "bad pattern : %s", argv[-1]);
zwarnnam(name, "bad pattern : %s", asg->name);
returnval = 1;
continue;
}
if (OPT_PLUS(ops,'m') && !asg->value) {
if (OPT_PLUS(ops,'m') && !ASG_VALUEP(asg)) {
scanmatchtable(paramtab, pprog, 1, on|roff, 0,
paramtab->printnode, printflags);
continue;
@ -2648,7 +2787,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
for (pmnode = firstnode(pmlist); pmnode; incnode(pmnode)) {
pm = (Param) getdata(pmnode);
if (!typeset_single(name, pm->node.nam, pm, func, on, off, roff,
asg->value, NULL, ops, 0))
asg, NULL, ops, 0))
returnval = 1;
}
}
@ -2657,7 +2796,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
}
/* Take arguments literally. Don't glob */
while ((asg = getasg(*argv++))) {
while ((asg = getasg(&argv, assigns))) {
HashNode hn = (paramtab == realparamtab ?
gethashnode2(paramtab, asg->name) :
paramtab->getnode(paramtab, asg->name));
@ -2671,7 +2810,7 @@ bin_typeset(char *name, char **argv, Options ops, int func)
continue;
}
if (!typeset_single(name, asg->name, (Param)hn,
func, on, off, roff, asg->value, NULL,
func, on, off, roff, asg, NULL,
ops, 0))
returnval = 1;
}
@ -3514,7 +3653,7 @@ bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
}
queue_signals();
for (;*argv;++argv) {
while (*argv) {
void *hn;
if (OPT_ISSET(ops,'m')) {
/* with the -m option, treat the argument as a glob pattern */
@ -3529,12 +3668,12 @@ bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
}
continue;
}
if (!(asg = getasg(*argv))) {
if (!(asg = getasg(&argv, NULL))) {
zwarnnam(name, "bad assignment");
returnval = 1;
} else if (asg->value) {
} else if (ASG_VALUEP(asg)) {
if(isset(RESTRICTED)) {
zwarnnam(name, "restricted: %s", asg->value);
zwarnnam(name, "restricted: %s", asg->value.scalar);
returnval = 1;
} else {
/* The argument is of the form foo=bar, *
@ -3550,12 +3689,12 @@ bin_hash(char *name, char **argv, Options ops, UNUSED(int func))
} else {
Nameddir nd = hn = zshcalloc(sizeof *nd);
nd->node.flags = 0;
nd->dir = ztrdup(asg->value);
nd->dir = ztrdup(asg->value.scalar);
}
} else {
Cmdnam cn = hn = zshcalloc(sizeof *cn);
cn->node.flags = HASHED;
cn->u.cmd = ztrdup(asg->value);
cn->u.cmd = ztrdup(asg->value.scalar);
}
ht->addnode(ht, ztrdup(asg->name), hn);
if(OPT_ISSET(ops,'v'))
@ -3761,12 +3900,12 @@ bin_alias(char *name, char **argv, Options ops, UNUSED(int func))
/* Take arguments literally. Don't glob */
queue_signals();
while ((asg = getasg(*argv++))) {
if (asg->value && !OPT_ISSET(ops,'L')) {
while ((asg = getasg(&argv, NULL))) {
if (asg->value.scalar && !OPT_ISSET(ops,'L')) {
/* The argument is of the form foo=bar and we are not *
* forcing a listing with -L, so define an alias */
ht->addnode(ht, ztrdup(asg->name),
createaliasnode(ztrdup(asg->value), flags1));
createaliasnode(ztrdup(asg->value.scalar), flags1));
} else if ((a = (Alias) ht->getnode(ht, asg->name))) {
/* display alias if appropriate */
if (!type_opts || ht == sufaliastab ||

View File

@ -2437,13 +2437,13 @@ execcmd(Estate state, int input, int output, int how, int last1)
char *text;
int save[10];
int fil, dfil, is_cursh, type, do_exec = 0, redir_err = 0, i, htok = 0;
int nullexec = 0, assign = 0, forked = 0;
int nullexec = 0, assign = 0, forked = 0, postassigns = 0;
int is_shfunc = 0, is_builtin = 0, is_exec = 0, use_defpath = 0;
/* Various flags to the command. */
int cflags = 0, orig_cflags = 0, checked = 0, oautocont = -1;
LinkList redir;
wordcode code;
Wordcode beg = state->pc, varspc;
Wordcode beg = state->pc, varspc, assignspc = (Wordcode)0;
FILE *oxtrerr = xtrerr, *newxtrerr = NULL;
doneps4 = 0;
@ -2464,8 +2464,28 @@ execcmd(Estate state, int input, int output, int how, int last1)
/* It would be nice if we could use EC_DUPTOK instead of EC_DUP here.
* But for that we would need to check/change all builtins so that
* they don't modify their argument strings. */
args = (type == WC_SIMPLE ?
ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP, &htok) : NULL);
switch (type) {
case WC_SIMPLE:
args = ecgetlist(state, WC_SIMPLE_ARGC(code), EC_DUP, &htok);
break;
case WC_TYPESET:
args = ecgetlist(state, WC_TYPESET_ARGC(code), EC_DUP, &htok);
postassigns = *state->pc++;
assignspc = state->pc;
for (i = 0; i < postassigns; i++) {
code = *state->pc;
DPUTS(wc_code(code) != WC_ASSIGN,
"BUG: miscounted typeset assignments");
state->pc += (WC_ASSIGN_TYPE(code) == WC_ASSIGN_SCALAR ?
3 : WC_ASSIGN_NUM(code) + 2);
}
break;
default:
args = NULL;
}
/*
* If assignment but no command get the status from variable
* assignment.
@ -2488,7 +2508,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
/* If the command begins with `%', then assume it is a *
* reference to a job in the job table. */
if (type == WC_SIMPLE && args && nonempty(args) &&
if ((type == WC_SIMPLE || type == WC_TYPESET) && args && nonempty(args) &&
*(char *)peekfirst(args) == '%') {
if (how & Z_DISOWN) {
oautocont = opts[AUTOCONTINUE];
@ -2517,20 +2537,32 @@ execcmd(Estate state, int input, int output, int how, int last1)
* command if it contains some tokens (e.g. x=ex; ${x}port), so this *
* only works in simple cases. has_token() is called to make sure *
* this really is a simple case. */
if (type == WC_SIMPLE) {
if (type == WC_SIMPLE || type == WC_TYPESET) {
while (args && nonempty(args)) {
char *cmdarg = (char *) peekfirst(args);
checked = !has_token(cmdarg);
if (!checked)
break;
if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
(hn = shfunctab->getnode(shfunctab, cmdarg))) {
is_shfunc = 1;
break;
}
if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
checked = !(cflags & BINF_BUILTIN);
break;
if (type == WC_TYPESET &&
(hn = builtintab->getnode2(builtintab, cmdarg))) {
/*
* If reserved word for typeset command found (and so
* enabled), use regardless of whether builtin is
* enabled as we share the implementation.
*
* Reserved words take precedence over shell functions.
*/
checked = 1;
} else {
if (!(cflags & (BINF_BUILTIN | BINF_COMMAND)) &&
(hn = shfunctab->getnode(shfunctab, cmdarg))) {
is_shfunc = 1;
break;
}
if (!(hn = builtintab->getnode(builtintab, cmdarg))) {
checked = !(cflags & BINF_BUILTIN);
break;
}
}
orig_cflags |= cflags;
cflags &= ~BINF_BUILTIN & ~BINF_COMMAND;
@ -2661,7 +2693,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
if (args && htok)
prefork(args, esprefork);
if (type == WC_SIMPLE) {
if (type == WC_SIMPLE || type == WC_TYPESET) {
int unglobbed = 0;
for (;;) {
@ -2897,7 +2929,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
return;
}
if (type == WC_SIMPLE && !nullexec) {
if ((type == WC_SIMPLE || type == WC_TYPESET) && !nullexec) {
char *s;
char trycd = (isset(AUTOCD) && isset(SHINSTDIN) &&
(!redir || empty(redir)) && args && !empty(args) &&
@ -3457,9 +3489,117 @@ execcmd(Estate state, int input, int output, int how, int last1)
execshfunc((Shfunc) hn, args);
} else {
/* It's a builtin */
LinkList assigns = (LinkList)0;
if (forked)
closem(FDT_INTERNAL);
lastval = execbuiltin(args, (Builtin) hn);
if (postassigns) {
Wordcode opc = state->pc;
state->pc = assignspc;
assigns = newlinklist();
while (postassigns--) {
wordcode ac = *state->pc++;
char *name = ecgetstr(state, EC_DUPTOK, &htok);
Asgment asg;
local_list1(svl);
DPUTS(wc_code(ac) != WC_ASSIGN,
"BUG: bad assignment list for typeset");
if (htok) {
init_list1(svl, name);
if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR &&
WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
char *data;
/*
* Special case: this is a name only, so
* it's not required to be a single
* expansion. Furthermore, for
* consistency with the builtin
* interface, it may expand into
* scalar assignments:
* ass=(one=two three=four)
* typeset a=b $ass
*/
/* Unused dummy value for name */
(void)ecgetstr(state, EC_DUPTOK, &htok);
prefork(&svl, PREFORK_TYPESET);
if (errflag) {
state->pc = opc;
break;
}
globlist(&svl, 0);
if (errflag) {
state->pc = opc;
break;
}
while ((data = ugetnode(&svl))) {
char *ptr;
asg = (Asgment)zhalloc(sizeof(struct asgment));
asg->is_array = 0;
if ((ptr = strchr(data, '='))) {
*ptr++ = '\0';
asg->name = data;
asg->value.scalar = ptr;
} else {
asg->name = data;
asg->value.scalar = NULL;
}
uaddlinknode(assigns, &asg->node);
}
continue;
}
prefork(&svl, PREFORK_SINGLE);
name = empty(&svl) ? "" :
(char *)getdata(firstnode(&svl));
}
untokenize(name);
asg = (Asgment)zhalloc(sizeof(struct asgment));
asg->name = name;
if (WC_ASSIGN_TYPE(ac) == WC_ASSIGN_SCALAR) {
char *val = ecgetstr(state, EC_DUPTOK, &htok);
asg->is_array = 0;
if (WC_ASSIGN_TYPE2(ac) == WC_ASSIGN_INC) {
/* Fake assignment, no value */
asg->value.scalar = NULL;
} else {
if (htok) {
init_list1(svl, val);
prefork(&svl, PREFORK_SINGLE|PREFORK_ASSIGN);
if (errflag) {
state->pc = opc;
break;
}
/*
* No globassign for typeset
* arguments, thank you
*/
val = empty(&svl) ? "" :
(char *)getdata(firstnode(&svl));
}
untokenize(val);
asg->value.scalar = val;
}
} else {
asg->is_array = 1;
asg->value.array =
ecgetlist(state, WC_ASSIGN_NUM(ac),
EC_DUPTOK, &htok);
prefork(asg->value.array, PREFORK_ASSIGN);
if (errflag) {
state->pc = opc;
break;
}
globlist(asg->value.array, 0);
if (errflag) {
state->pc = opc;
break;
}
}
uaddlinknode(assigns, &asg->node);
}
state->pc = opc;
}
lastval = execbuiltin(args, assigns, (Builtin) hn);
fflush(stdout);
if (save[1] == -2) {
if (ferror(stdout)) {
@ -3501,7 +3641,7 @@ execcmd(Estate state, int input, int output, int how, int last1)
if (!subsh && isset(RCS) && interact && !nohistsave)
savehistfile(NULL, 1, HFILE_USE_OPTIONS);
}
if (type == WC_SIMPLE) {
if (type == WC_SIMPLE || type == WC_TYPESET) {
if (varspc) {
int addflags = ADDVAR_EXPORT|ADDVAR_RESTRICT;
if (forked)

View File

@ -1050,22 +1050,29 @@ static struct reswd reswds[] = {
{{NULL, "}", 0}, OUTBRACE},
{{NULL, "case", 0}, CASE},
{{NULL, "coproc", 0}, COPROC},
{{NULL, "declare", 0}, TYPESET},
{{NULL, "do", 0}, DOLOOP},
{{NULL, "done", 0}, DONE},
{{NULL, "elif", 0}, ELIF},
{{NULL, "else", 0}, ELSE},
{{NULL, "end", 0}, ZEND},
{{NULL, "esac", 0}, ESAC},
{{NULL, "export", 0}, TYPESET},
{{NULL, "fi", 0}, FI},
{{NULL, "float", 0}, TYPESET},
{{NULL, "for", 0}, FOR},
{{NULL, "foreach", 0}, FOREACH},
{{NULL, "function", 0}, FUNC},
{{NULL, "if", 0}, IF},
{{NULL, "integer", 0}, TYPESET},
{{NULL, "local", 0}, TYPESET},
{{NULL, "nocorrect", 0}, NOCORRECT},
{{NULL, "readonly", 0}, TYPESET},
{{NULL, "repeat", 0}, REPEAT},
{{NULL, "select", 0}, SELECT},
{{NULL, "then", 0}, THEN},
{{NULL, "time", 0}, TIME},
{{NULL, "typeset", 0}, TYPESET},
{{NULL, "until", 0}, UNTIL},
{{NULL, "while", 0}, WHILE},
{{NULL, NULL, 0}, 0}

View File

@ -1182,7 +1182,7 @@ gettokstr(int c, int sub)
c = Outpar;
}
} else if (peek != ENVSTRING &&
incmdpos && !bct && !brct) {
(incmdpos || intypeset) && !bct && !brct) {
char *t = tokstr;
if (idigit(*t))
while (++t < lexbuf.ptr && idigit(*t));
@ -1200,7 +1200,7 @@ gettokstr(int c, int sub)
t++;
if (t == lexbuf.ptr) {
e = hgetc();
if (e == '(' && incmdpos) {
if (e == '(') {
*lexbuf.ptr = '\0';
return ENVARRAY;
}

View File

@ -172,7 +172,7 @@ static struct optname optns[] = {
{{NULL, "kshautoload", OPT_EMULATE|OPT_BOURNE}, KSHAUTOLOAD},
{{NULL, "kshglob", OPT_EMULATE|OPT_KSH}, KSHGLOB},
{{NULL, "kshoptionprint", OPT_EMULATE|OPT_KSH}, KSHOPTIONPRINT},
{{NULL, "kshtypeset", OPT_EMULATE|OPT_KSH}, KSHTYPESET},
{{NULL, "kshtypeset", 0}, KSHTYPESET},
{{NULL, "kshzerosubscript", 0}, KSHZEROSUBSCRIPT},
{{NULL, "listambiguous", OPT_ALL}, LISTAMBIGUOUS},
{{NULL, "listbeep", OPT_ALL}, LISTBEEP},

View File

@ -63,6 +63,11 @@ int isnewlin;
/**/
int infor;
/* != 0 if parsing arguments of typeset etc. */
/**/
int intypeset;
/* list of here-documents */
/**/
@ -118,11 +123,20 @@ struct heredocs *hdocs;
* WC_ASSIGN
* - data contains type (scalar, array) and number of array-elements
* - followed by name and value
* Note variant for WC_TYPESET assignments: WC_ASSIGN_INC indicates
* a name with no equals, not an =+ which isn't valid here.
*
* WC_SIMPLE
* - data contains the number of arguments (plus command)
* - followed by strings
*
* WC_TYPESET
* Variant of WC_SIMPLE used when TYPESET reserved word found.
* - data contains the number of string arguments (plus command)
* - followed by strings
* - followed by number of assignments
* - followed by assignments if non-zero number.
*
* WC_SUBSH
* - data unused
* - followed by list
@ -257,6 +271,7 @@ parse_context_save(struct parse_stack *ps, int toplevel)
ps->incasepat = incasepat;
ps->isnewlin = isnewlin;
ps->infor = infor;
ps->intypeset = intypeset;
ps->hdocs = hdocs;
ps->eclen = eclen;
@ -290,6 +305,7 @@ parse_context_restore(const struct parse_stack *ps, int toplevel)
incasepat = ps->incasepat;
isnewlin = ps->isnewlin;
infor = ps->infor;
intypeset = ps->intypeset;
hdocs = ps->hdocs;
eclen = ps->eclen;
@ -430,7 +446,7 @@ init_parse_status(void)
* between the two it's a bit ambiguous. We clear them when
* using the lexical analyser for strings as well as here.
*/
incasepat = incond = inredir = infor = 0;
incasepat = incond = inredir = infor = intypeset = 0;
incmdpos = 1;
}
@ -992,6 +1008,7 @@ par_cmd(int *cmplx, int zsh_construct)
incmdpos = 1;
incasepat = 0;
incond = 0;
intypeset = 0;
return 1;
}
@ -1709,7 +1726,8 @@ static int
par_simple(int *cmplx, int nr)
{
int oecused = ecused, isnull = 1, r, argc = 0, p, isfunc = 0, sr = 0;
int c = *cmplx, nrediradd, assignments = 0;
int c = *cmplx, nrediradd, assignments = 0, ppost = 0, is_typeset = 0;
wordcode postassigns = 0;
r = ecused;
for (;;) {
@ -1717,31 +1735,32 @@ par_simple(int *cmplx, int nr)
*cmplx = c = 1;
nocorrect = 1;
} else if (tok == ENVSTRING) {
char *p, *name, *str;
char *ptr, *name, *str;
name = tokstr;
for (p = tokstr; *p && *p != Inbrack && *p != '=' && *p != '+';
p++);
if (*p == Inbrack) skipparens(Inbrack, Outbrack, &p);
if (*p == '+') {
*p++ = '\0';
for (ptr = tokstr;
*ptr && *ptr != Inbrack && *ptr != '=' && *ptr != '+';
ptr++);
if (*ptr == Inbrack) skipparens(Inbrack, Outbrack, &ptr);
if (*ptr == '+') {
*ptr++ = '\0';
ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
} else
ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_NEW, 0));
if (*p == '=') {
*p = '\0';
str = p + 1;
if (*ptr == '=') {
*ptr = '\0';
str = ptr + 1;
} else
equalsplit(tokstr, &str);
for (p = str; *p; p++) {
for (ptr = str; *ptr; ptr++) {
/*
* We can't treat this as "simple" if it contains
* expansions that require process subsitution, since then
* we need process handling.
*/
if (p[1] == Inpar &&
(*p == Equals || *p == Inang || *p == OutangProc)) {
if (ptr[1] == Inpar &&
(*ptr == Equals || *ptr == Inang || *ptr == OutangProc)) {
*cmplx = 1;
break;
}
@ -1786,14 +1805,18 @@ par_simple(int *cmplx, int nr)
p = ecadd(WCB_SIMPLE(0));
for (;;) {
if (tok == STRING) {
if (tok == STRING || tok == TYPESET) {
int redir_var = 0;
*cmplx = 1;
incmdpos = 0;
if (tok == TYPESET)
intypeset = is_typeset = 1;
if (!isset(IGNOREBRACES) && *tokstr == Inbrace)
{
/* Look for redirs of the form {var}>file etc. */
char *eptr = tokstr + strlen(tokstr) - 1;
char *ptr = eptr;
@ -1824,8 +1847,21 @@ par_simple(int *cmplx, int nr)
if (!redir_var)
{
ecstr(tokstr);
argc++;
if (postassigns) {
/*
* We're in the variable part of a typeset,
* but this doesn't have an assignment.
* We'll parse it as if it does, but mark
* it specially with WC_ASSIGN_INC.
*/
postassigns++;
ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_INC, 0));
ecstr(tokstr);
ecstr(""); /* TBD can possibly optimise out */
} else {
ecstr(tokstr);
argc++;
}
zshlex();
}
} else if (IS_REDIROP(tok)) {
@ -1833,6 +1869,50 @@ par_simple(int *cmplx, int nr)
nrediradd = par_redir(&r, NULL);
p += nrediradd;
sr += nrediradd;
} else if (tok == ENVSTRING) {
char *ptr, *name, *str;
if (!postassigns++)
ppost = ecadd(0);
name = tokstr;
for (ptr = tokstr; *ptr && *ptr != Inbrack && *ptr != '=' && *ptr != '+';
ptr++);
if (*ptr == Inbrack) skipparens(Inbrack, Outbrack, &ptr);
ecadd(WCB_ASSIGN(WC_ASSIGN_SCALAR, WC_ASSIGN_NEW, 0));
if (*ptr == '=') {
*ptr = '\0';
str = ptr + 1;
} else
equalsplit(tokstr, &str);
ecstr(name);
ecstr(str);
zshlex();
} else if (tok == ENVARRAY) {
int n, parr;
if (!postassigns++)
ppost = ecadd(0);
parr = ecadd(0);
ecstr(tokstr);
cmdpush(CS_ARRAY);
/*
* Careful here: this must be the typeset case,
* but we need to tell the lexer not to look
* for assignments until we've finished the
* present one.
*/
intypeset = 0;
zshlex();
n = par_nl_wordlist();
ecbuf[parr] = WCB_ASSIGN(WC_ASSIGN_ARRAY, WC_ASSIGN_NEW, n);
cmdpop();
intypeset = 1;
if (tok != OUTPAR)
YYERROR(oecused);
zshlex();
} else if (tok == INOUTPAR) {
zlong oldlineno = lineno;
int onp, so, oecssub = ecssub;
@ -1841,7 +1921,7 @@ par_simple(int *cmplx, int nr)
if (!isset(MULTIFUNCDEF) && argc > 1)
YYERROR(oecused);
/* Error if preceding assignments */
if (assignments)
if (assignments || postassigns)
YYERROR(oecused);
*cmplx = c;
@ -1947,9 +2027,18 @@ par_simple(int *cmplx, int nr)
return 0;
}
incmdpos = 1;
intypeset = 0;
if (!isfunc)
ecbuf[p] = WCB_SIMPLE(argc);
if (!isfunc) {
if (is_typeset) {
ecbuf[p] = WCB_TYPESET(argc);
if (postassigns)
ecbuf[ppost] = postassigns;
else
ecadd(0);
} else
ecbuf[p] = WCB_SIMPLE(argc);
}
return sr + 1;
}

View File

@ -155,6 +155,46 @@ taddlist(Estate state, int num)
}
}
/* add an assignment */
static void
taddassign(wordcode code, Estate state, int typeset)
{
/* name */
taddstr(ecgetstr(state, EC_NODUP, NULL));
/* value... maybe */
if (WC_ASSIGN_TYPE2(code) == WC_ASSIGN_INC) {
if (typeset) {
/* dummy assignment --- just var name */
(void)ecgetstr(state, EC_NODUP, NULL);
taddchr(' ');
return;
}
taddchr('+');
}
taddchr('=');
if (WC_ASSIGN_TYPE(code) == WC_ASSIGN_ARRAY) {
taddchr('(');
taddlist(state, WC_ASSIGN_NUM(code));
taddstr(") ");
} else {
taddstr(ecgetstr(state, EC_NODUP, NULL));
taddchr(' ');
}
}
/* add a number of assignments from typeset */
/**/
static void
taddassignlist(Estate state, wordcode count)
{
while (count--) {
wordcode code = *state->pc++;
taddassign(code, state, 1);
}
}
/* add a newline, or something equivalent, to the text buffer */
/**/
@ -439,22 +479,17 @@ gettext2(Estate state)
}
break;
case WC_ASSIGN:
taddstr(ecgetstr(state, EC_NODUP, NULL));
if (WC_ASSIGN_TYPE2(code) == WC_ASSIGN_INC) taddchr('+');
taddchr('=');
if (WC_ASSIGN_TYPE(code) == WC_ASSIGN_ARRAY) {
taddchr('(');
taddlist(state, WC_ASSIGN_NUM(code));
taddstr(") ");
} else {
taddstr(ecgetstr(state, EC_NODUP, NULL));
taddchr(' ');
}
taddassign(code, state, 0);
break;
case WC_SIMPLE:
taddlist(state, WC_SIMPLE_ARGC(code));
stack = 1;
break;
case WC_TYPESET:
taddlist(state, WC_TYPESET_ARGC(code));
taddassignlist(state, *state->pc++);
stack = 1;
break;
case WC_SUBSH:
if (!s) {
taddstr("(");

View File

@ -336,7 +336,8 @@ enum lextok {
THEN, /* then */
TIME, /* time */ /* 60 */
UNTIL, /* until */
WHILE /* while */
WHILE, /* while */
TYPESET /* typeset or similar */
};
/* Redirection types. If you modify this, you may also have to modify *
@ -671,14 +672,6 @@ struct multio {
int fds[MULTIOUNIT]; /* list of src/dests redirected to/from this fd */
};
/* structure for foo=bar assignments */
struct asgment {
struct asgment *next;
char *name;
char *value;
};
/* lvalue for variable assignment/expansion */
struct value {
@ -789,23 +782,24 @@ struct eccstr {
#define WC_REDIR 4
#define WC_ASSIGN 5
#define WC_SIMPLE 6
#define WC_SUBSH 7
#define WC_CURSH 8
#define WC_TIMED 9
#define WC_FUNCDEF 10
#define WC_FOR 11
#define WC_SELECT 12
#define WC_WHILE 13
#define WC_REPEAT 14
#define WC_CASE 15
#define WC_IF 16
#define WC_COND 17
#define WC_ARITH 18
#define WC_AUTOFN 19
#define WC_TRY 20
#define WC_TYPESET 7
#define WC_SUBSH 8
#define WC_CURSH 9
#define WC_TIMED 10
#define WC_FUNCDEF 11
#define WC_FOR 12
#define WC_SELECT 13
#define WC_WHILE 14
#define WC_REPEAT 15
#define WC_CASE 16
#define WC_IF 17
#define WC_COND 18
#define WC_ARITH 19
#define WC_AUTOFN 20
#define WC_TRY 21
/* increment as necessary */
#define WC_COUNT 21
#define WC_COUNT 22
#define WCB_END() wc_bld(WC_END, 0)
@ -849,6 +843,12 @@ struct eccstr {
#define WC_ASSIGN_SCALAR 0
#define WC_ASSIGN_ARRAY 1
#define WC_ASSIGN_NEW 0
/*
* In normal assignment, this indicate += to append.
* In assignment following a typeset, where that's not allowed,
* we overload this to indicate a variable without an
* assignment.
*/
#define WC_ASSIGN_INC 1
#define WC_ASSIGN_NUM(C) (wc_data(C) >> 2)
#define WCB_ASSIGN(T,A,N) wc_bld(WC_ASSIGN, ((T) | ((A) << 1) | ((N) << 2)))
@ -856,6 +856,9 @@ struct eccstr {
#define WC_SIMPLE_ARGC(C) wc_data(C)
#define WCB_SIMPLE(N) wc_bld(WC_SIMPLE, (N))
#define WC_TYPESET_ARGC(C) wc_data(C)
#define WCB_TYPESET(N) wc_bld(WC_TYPESET, (N))
#define WC_SUBSH_SKIP(C) wc_data(C)
#define WCB_SUBSH(O) wc_bld(WC_SUBSH, (O))
@ -1140,6 +1143,34 @@ struct alias {
/* is this an alias for suffix handling? */
#define ALIAS_SUFFIX (1<<2)
/* structure for foo=bar assignments */
struct asgment {
struct linknode node;
char *name;
int is_array;
union {
char *scalar;
LinkList array;
} value;
};
/*
* Assignment is array?
*/
#define ASG_ARRAYP(asg) ((asg)->is_array)
/*
* Assignment has value?
* We need to arrange for each of the values
* to be the same type or the compiler will
* get fed up.
*/
#define ASG_VALUEP(asg) (ASG_ARRAYP(asg) ? \
((asg)->value.array != (LinkList)0) : \
((asg)->value.scalar != (char *)0))
/* node in command path hash table (cmdnamtab) */
struct cmdnam {
@ -1268,6 +1299,7 @@ struct options {
*/
typedef int (*HandlerFunc) _((char *, char **, Options, int));
typedef int (*HandlerFuncAssign) _((char *, char **, LinkList, Options, int));
#define NULLBINCMD ((HandlerFunc) 0)
struct builtin {
@ -1311,6 +1343,12 @@ struct builtin {
* does not terminate options.
*/
#define BINF_HANDLES_OPTS (1<<18)
/*
* Handles the assignement interface. The argv list actually contains
* two nested litsts, the first of normal arguments, and the second of
* assignment structures.
*/
#define BINF_ASSIGN (1<<19)
struct module {
struct hashnode node;
@ -2779,6 +2817,7 @@ struct parse_stack {
int incasepat;
int isnewlin;
int infor;
int intypeset;
int eclen, ecused, ecnpats;
Wordcode ecbuf;

View File

@ -22,6 +22,8 @@
%prep
mkdir typeset.tmp && cd typeset.tmp
setopt noglob
scalar=scalar
@ -231,7 +233,7 @@
typeset -T THIS will not work
1:Tied array syntax
?(eval):typeset:1: -T requires names of scalar and array
?(eval):typeset:1: too many arguments for -T
local array[2]=x
1:Illegal local array element assignment
@ -508,3 +510,144 @@
>a2=(three four)
>typeset -r r1=yes
>typeset -r r2=no
one=hidden two=hidden three=hidden four=hidden five=hidden
fn() {
local bleugh="four=vier"
typeset -R10 one=eins two=(zwei dio) three $bleugh five=(cinq cinque)
three=drei
print -l $one $two $three $four $five
}
fn
print -l $one $two $three $four $five
0:typeset reserved word interface: basic
> eins
>zwei
>dio
> drei
> vier
>cinq
>cinque
>hidden
>hidden
>hidden
>hidden
>hidden
(
setopt glob
mkdir -p arrayglob
touch arrayglob/{one,two,three,four,five,six,seven}
fn() {
typeset array=(arrayglob/[tf]*)
print -l ${array:t}
#
typeset {first,second,third}=the_same_value array=(
extends
over
multiple
lines
)
print -l $first $second $third "$array"
#
integer i=$(echo 1 + 2 + 3 + 4)
print $i
#
# only noted by accident this was broken..
# we need to turn off special recognition
# of assignments within assignments...
typeset careful=( i=1 j=2 k=3 )
print -l $careful
}
fn
)
0:typeset reserved word, more complicated cases
>five
>four
>three
>two
>the_same_value
>the_same_value
>the_same_value
>extends over multiple lines
>10
>i=1
>j=2
>k=3
(
# reserved word is recognised at parsing.
# yes, this is documented.
# anyway, that means we need to
# re-eval the function...
fn='
fn() {
typeset foo=`echo one word=two`
print $foo
print $word
}
'
print reserved
eval $fn; fn
print builtin
disable -r typeset
eval $fn; fn
enable -r typeset
disable typeset
print reserved
eval $fn; fn
)
0:reserved word and builtin interfaces
>reserved
>one word=two
>
>builtin
>one
>two
>reserved
>one word=two
>
fn() {
emulate -L zsh
setopt typeset_silent
local k
typeset -A hash=(k1 v1 k2 v2)
typeset foo=word array=(more than one word)
for k in ${(ko)hash}; do
print $k $hash[$k]
done
print -l $foo $array
typeset -A hash
typeset foo array
for k in ${(ko)hash}; do
print $k $hash[$k]
done
print -l $foo $array
typeset hash=(k3 v3 k4 v4) array=(odd number here)
for k in ${(ko)hash}; do
print $k $hash[$k]
done
print -l $array
}
fn
0:typeset preserves existing variable types
>k1 v1
>k2 v2
>word
>more
>than
>one
>word
>k1 v1
>k2 v2
>word
>more
>than
>one
>word
>k3 v3
>k4 v4
>odd
>number
>here

View File

@ -199,5 +199,5 @@
?+zsh_directory_name:4> [[ d == n ]]
?+zsh_directory_name:12> [[ <parent>/very_long_directory_name == (#b)(*)/very_long_directory_name ]]
?+zsh_directory_name:14> return 0
?+fn:7> local 'd=~[<parent>:l]'
?+fn:7> local d='~[<parent>:l]'
?+fn:8> print '~[<parent>:l]'

View File

@ -570,6 +570,15 @@
>unset
>globassign
# This test is now somewhat artificial as
# KSH_TYPESET only applies to the builtin
# interface. Tests to the more standard
# reserved word interface appear elsewhere.
(
# reserved words are handled during parsing,
# hence eval...
disable -r typeset
eval '
setopt kshtypeset
ktvars=(ktv1 ktv2)
typeset ktfoo=`echo arg1 arg2` $ktvars
@ -580,6 +589,8 @@
print $noktfoo
print $+noktarg1 $+noktarg2
unset ktfoo ktv1 ktv2 noktfoo noktarg2
'
)
0:KSH_TYPESET option
>1 1 0
>arg1 arg2