Subject: Re: zparseopts varieties
From: Bart Schaefer <[email-masked]>
To: Zsh hackers list <[email-masked]>
Date: Tue, 26 Jan 2016 19:00:44 -0800
List-Id: Zsh Workers List <zsh-workers.zsh.org>

On Jan 26,  9:53am, Sebastian Gniazdowski wrote:
}
} Argument of -M will not be put into Mopt associative array:
} 
} % typeset -A Mopt; set -- -M arg; zparseopts -D -A Mopt "M::"; echo
} ${(kv)Mopt}; echo "1: $1, 2: $2"
} -M
} 1: arg, 2:

Curious.

The code to handle the argument in the next word is unreachable in the
optional case, and it appears to have been so ever since zparseopts was
invented; or, at least, as long as we've had code in version control.

Also, there's this:

torch% zparseopts -A opts -DE b: c
 ../../zsh-5.0/Src/params.c:2986: BUG: assigning to undeclared associative array

Which actually is probably a case of an over-aggressive DPUTS assertion
(that I added a few months ago) rather than an actual bug.

} Also, double colon (optionality of argument) cannot take array name:

This is intentional: you aren't meant to use an associative array with
the =array form, double colon or not.

torch% typeset -A Mopt
torch% set -- -b -c
torch% zparseopts b=Mopt
zsh: bad set of key/value pairs for associative array

It accidentally works to use the same associative array for an even number
of options, but the results are wrong -- the options alternate as keys
and values:

torch% zparseopts c=Mopt b=Mopt
torch% print -rl -- ${(k)Mopt}
-b
torch% print -rl -- ${(v)Mopt}
-c

I scanned the distributed functions for uses of zparseopts with "::" and
did not find any, so hopefully the following won't break anything.

diff --git a/Doc/Zsh/mod_zutil.yo b/Doc/Zsh/mod_zutil.yo
index fd6f2f3..dc17161 100644
--- a/Doc/Zsh/mod_zutil.yo
+++ b/Doc/Zsh/mod_zutil.yo
@@ -179,13 +179,14 @@ item(tt(zregexparse))(
 This implements some internals of the tt(_regex_arguments) function.
 )
 findex(zparseopts)
-item(tt(zparseopts) [ tt(-DKME) ] [ tt(-a) var(array) ] [ tt(-A) var(assoc) ] var(spec) ...)(
+item(tt(zparseopts) [ tt(-D) tt(-K) tt(-M) tt(-E) ] [ tt(-a) var(array) ] [ tt(-A) var(assoc) ] var(spec) ...)(
 This builtin simplifies the parsing of options in positional parameters,
 i.e. the set of arguments given by tt($*).  Each var(spec) describes one
 option and must be of the form `var(opt)[tt(=)var(array)]'.  If an option
 described by var(opt) is found in the positional parameters it is copied
 into the var(array) specified with the tt(-a) option; if the optional
-`tt(=)var(array)' is given, it is instead copied into that array.
+`tt(=)var(array)' is given, it is instead copied into that array, which
+should be declared as a normal array and never as an associative array.
 
 Note that it is an error to give any var(spec) without an
 `tt(=)var(array)' unless one of the tt(-a) or tt(-A) options is used.
@@ -232,7 +233,10 @@ first colon.
 )
 enditem()
 
-The options of tt(zparseopts) itself are:
+The options of tt(zparseopts) itself cannot be stacked because, for
+example, the stack `tt(-DEK)' is indistinguishable from a var(spec) for
+the GNU-style long option `tt(--DEK)'.  The options of tt(zparseopts)
+itself are:
 
 startitem()
 item(tt(-a) var(array))(
diff --git a/Src/Modules/zutil.c b/Src/Modules/zutil.c
index 376cd84..d98028a 100644
--- a/Src/Modules/zutil.c
+++ b/Src/Modules/zutil.c
@@ -1833,7 +1833,8 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 		    if (o[1]) {
 			add_opt_val(d, o + 1);
 			break;
-		    } else if (!(d->flags & ZOF_OPT)) {
+		    } else if (!(d->flags & ZOF_OPT) ||
+			       (pp[1] && pp[1][0] != '-')) {
 			if (!pp[1]) {
 			    zwarnnam(nam, "missing argument for option: %s",
 				    d->name);
@@ -1859,7 +1860,8 @@ bin_zparseopts(char *nam, char **args, UNUSED(Options ops), UNUSED(int func))
 
 		if (*e)
 		    add_opt_val(d, e);
-		else if (!(d->flags & ZOF_OPT)) {
+		else if (!(d->flags & ZOF_OPT) ||
+			 (pp[1] && pp[1][0] != '-')) {
 		    if (!pp[1]) {
 			zwarnnam(nam, "missing argument for option: %s",
 				d->name);