about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPeter Stephenson <pws@users.sourceforge.net>2007-06-18 13:25:03 +0000
committerPeter Stephenson <pws@users.sourceforge.net>2007-06-18 13:25:03 +0000
commitabae4fe16e26cf027e7c8165d27b93d74bbd18b2 (patch)
tree811bae4da4a0b6f0b370334a7f01ee5d9b9c8baf
parent5c44b0a472c6e9efdec28ef03337c484b1bc3ce1 (diff)
downloadzsh-abae4fe16e26cf027e7c8165d27b93d74bbd18b2.tar.gz
zsh-abae4fe16e26cf027e7c8165d27b93d74bbd18b2.tar.xz
zsh-abae4fe16e26cf027e7c8165d27b93d74bbd18b2.zip
23562: add KSH_ZERO_SUBSCRIPT option and leave off by default
-rw-r--r--ChangeLog14
-rw-r--r--Doc/Zsh/options.yo25
-rw-r--r--Doc/Zsh/params.yo26
-rw-r--r--README29
-rw-r--r--Src/Modules/mapfile.c2
-rw-r--r--Src/Modules/parameter.c10
-rw-r--r--Src/Zle/complete.c2
-rw-r--r--Src/glob.c2
-rw-r--r--Src/options.c5
-rw-r--r--Src/params.c89
-rw-r--r--Src/subst.c14
-rw-r--r--Src/zsh.h8
-rw-r--r--Test/C01arith.ztst9
-rw-r--r--Test/D05array.ztst8
-rw-r--r--Test/D06subscript.ztst53
-rw-r--r--Test/D07multibyte.ztst2
-rw-r--r--Test/E01options.ztst2
17 files changed, 225 insertions, 75 deletions
diff --git a/ChangeLog b/ChangeLog
index 5133192e6..6ac69f124 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+2007-06-18  Peter Stephenson  <pws@csr.com>
+
+	* 23562: README, Doc/Zsh/options.yo, Doc/Zsh/params.yo,
+	Src/exec.c, Src/glob.c, Src/options.c, Src/params.c, Src/subst.c,
+	Src/zsh.h, Src/Modules/mapfile.c, Src/Modules/parameter.c,
+	Src/Zle/complete.c, Test/C01arith.ztst, Test/D05array.ztst,
+	Test/D06subscript.ztst, Test/D07multibyte.ztst,
+	Test/E01options.ztst: add KSH_ZERO_SUBSCRIPT option to handle
+	the currently default behaviour that $array[0] or $string[0]
+	is mapped to $array[1] or $string[1] if KSH_ARRAYS is not
+	in effect.  Now off by default; returns empty element or
+	causes error if used for setting.  Valid ranges that happen
+	to include zero are not affected.
+
 2007-06-16  Peter Stephenson  <p.w.stephenson@ntlworld.com>
 
 	* unposted: Test/D07multibyte.ztst: stop test files from
diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo
index c8b651132..105870039 100644
--- a/Doc/Zsh/options.yo
+++ b/Doc/Zsh/options.yo
@@ -1244,6 +1244,31 @@ tt(readonly), are processed.  Without this option, zsh will perform normal
 word splitting after command and parameter expansion in arguments of an
 assignment; with it, word splitting does not take place in those cases.
 )
+pindex(KSH_ZERO_SUBSCRIPT)
+cindex(arrays, behaviour of index zero)
+item(tt(KSH_ZERO_SUBSCRIPT))(
+Treat use of a subscript of value zero in array or string expressions as a
+reference to the first element, i.e. the element that usually has the
+subscript 1.  Ignored if tt(KSH_ARRAYS) is also set.
+
+If neither this option nor tt(KSH_ARRAYS) is set, accesses to an element of
+an array or string with subscript zero return an empty element or string,
+while attempts to set element zero of an array or string are treated as an
+error.  However, attempts to set an otherwise valid subscript range that
+includes zero will succeed.  For example, if tt(KSH_ZERO_SUBSCRIPT) is not
+set,
+
+example(array[0]=(element))
+
+is an error, while
+
+example(array[0,1]=(element))
+
+is not and will replace the first element of the array.
+
+This option is for compatibility with older versions of the shell and
+is not recommended in new code.
+)
 pindex(POSIX_BUILTINS)
 item(tt(POSIX_BUILTINS) <K> <S>)(
 When this option is set the tt(command) builtin can be used to execute
diff --git a/Doc/Zsh/params.yo b/Doc/Zsh/params.yo
index 147c3a3ed..0e09335bb 100644
--- a/Doc/Zsh/params.yo
+++ b/Doc/Zsh/params.yo
@@ -96,6 +96,14 @@ Subscripts may be used inside braces used to delimit a parameter name, thus
 option is set, the braced form is the only one that works, as bracketed
 expressions otherwise are not treated as subscripts.
 
+If the tt(KSH_ARRAYS) option is not set, then by default accesses to
+an array element with a subscript that evaluates to zero return an
+empty string, while an attempt to write such an element is treated as
+an error.  For backward compatibility the tt(KSH_ZERO_SUBSCRIPT)
+option can be set to cause subscript values 0 and 1 to be equivalent; see
+the description of the option in ifzman(zmanref(zshoptions))\
+ifnzman(noderef(Description of Options)).
+
 The same subscripting syntax is used for associative arrays, except that
 no arithmetic expansion is applied to var(exp).  However, the parsing
 rules for arithmetic expressions still apply, which affects the way that
@@ -233,26 +241,22 @@ print ${array[(R)$key2]})
 item(tt(R))(
 Like `tt(r)', but gives the last match.  For associative arrays, gives
 all possible matches. May be used for assigning to ordinary array
-elements, but not for assigning to associative arrays.
-On failure the empty string is returned.
+elements, but not for assigning to associative arrays.  On failure, for
+normal arrays this has the effect of returning the element corresponding to
+subscript 0; this is empty unless one of the options tt(KSH_ARRAYS) or
+tt(KSH_ZERO_SUBSCRIPT) is in effect.
 )
 item(tt(i))(
 Like `tt(r)', but gives the index of the match instead; this may not be
 combined with a second argument.  On the left side of an assignment,
 behaves like `tt(r)'.  For associative arrays, the key part of each pair
 is compared to the pattern, and the first matching key found is the
-result.
-
-On failure, a value one past the end of the array or string is returned.
+result.  On failure substitutes one more than the last currently
+valid index, as discussed under the description of `tt(r)'.
 )
 item(tt(I))(
 Like `tt(i)', but gives the index of the last match, or all possible
-matching keys in an associative array.
-
-On failure the value 0 is returned.  If the option tt(KSH_ARRAYS) is in
-effect, the subscript is still 0 for a failed match; this cannot be
-distinguished from a successful match without testing tt(${array[0]})
-against the pattern.
+matching keys in an associative array.  On failure substitutes 0.
 )
 item(tt(k))(
 If used in a subscript on an associative array, this flag causes the keys
diff --git a/README b/README
index 034f5f39a..f8a7cf241 100644
--- a/README
+++ b/README
@@ -45,6 +45,31 @@ behaviour.)  Now it is treated identically to "$@".  The same change
 applies to expressions with forced splitting such as ${=1+"$@"}, but
 otherwise the case where SH_WORD_SPLIT is not set is unaffected.
 
+In previous versions of the shell it was possible to use index 0 in an
+array or string subscript to refer to the same element as index 1 if the
+option KSH_ARRAYS was not in effect.  This was a limited approximation to
+the full KSH_ARRAYS handling and so was not very useful.  In this version
+of the shell, this behaviour is only provided when the option
+KSH_ZERO_SUBSCRIPT is set.  Note that despite the name this does not provide
+true compatibility with ksh or other shells and KSH_ARRAYS should still be
+used for that purpose.  By default, the option is not set; an array
+subscript that evaluates to 0 returns an empty string or array element and
+attempts to write to an array or string range including only a zero
+subscript are treated as an error.  Writes to otherwise valid ranges that
+also include index zero are allowed; hence for example the assignment
+  array[(R)notfound,(r)notfound]=()
+(where the string "notfound" does not match an element in $array) sets the
+entire array to be empty, as in previous versions of the shell.
+KSH_ZERO_SUBSCRIPT is irrelevant when KSH_ARRAYS is set.  Also as in previous
+versions, attempts to write to non-existent elements at the end of an array
+cause the array to be suitably extended.  This difference means that, for
+example
+  array[(R)notfound]=(replacement)
+is an error if KSH_ZERO_SUBSCRIPT is not set (new behaviour), while
+  array[(r)notfound]=(replacement)
+causes the given value to be appended to the array (same behaviour as
+previous versions).
+
 The "exec" precommand modifier now takes various options for compatibility
 with other shells.  This means that whereas "exec -prog" previously
 tried to execute a command name "-prog", it will now report an error
@@ -77,10 +102,6 @@ of the value.  The form ${param//#$search/replace} where the value
 $search starts with "%" considers the "%" to be part of the search
 string as before.
 
-Parameter subscripts of the form ${array[(R)test]} now return the
-empty string if they fail to match.  The previous longstanding behaviour
-was confusing and useless.
-
 The MULTIBYTE option is on by default where it is available; this
 causes many operations to recognise characters as in the current locale.
 Older versions of the shell always assumed a character was one byte.
diff --git a/Src/Modules/mapfile.c b/Src/Modules/mapfile.c
index 25b506f03..fb46df885 100644
--- a/Src/Modules/mapfile.c
+++ b/Src/Modules/mapfile.c
@@ -149,7 +149,7 @@ setpmmapfiles(Param pm, HashTable ht)
 	    for (hn = ht->nodes[i]; hn; hn = hn->next) {
 		struct value v;
 
-		v.isarr = v.inv = v.start = 0;
+		v.isarr = v.flags = v.start = 0;
 		v.end = -1;
 		v.arr = NULL;
 		v.pm = (Param) hn;
diff --git a/Src/Modules/parameter.c b/Src/Modules/parameter.c
index 9d52bcd3f..bf21b5cda 100644
--- a/Src/Modules/parameter.c
+++ b/Src/Modules/parameter.c
@@ -180,7 +180,7 @@ setpmcommands(UNUSED(Param pm), HashTable ht)
 	    Cmdnam cn = zshcalloc(sizeof(*cn));
 	    struct value v;
 
-	    v.isarr = v.inv = v.start = 0;
+	    v.isarr = v.flags = v.start = 0;
 	    v.end = -1;
 	    v.arr = NULL;
 	    v.pm = (Param) hn;
@@ -341,7 +341,7 @@ setfunctions(UNUSED(Param pm), HashTable ht, int dis)
 	for (hn = ht->nodes[i]; hn; hn = hn->next) {
 	    struct value v;
 
-	    v.isarr = v.inv = v.start = 0;
+	    v.isarr = v.flags = v.start = 0;
 	    v.end = -1;
 	    v.arr = NULL;
 	    v.pm = (Param) hn;
@@ -701,7 +701,7 @@ setpmoptions(UNUSED(Param pm), HashTable ht)
 	    struct value v;
 	    char *val;
 
-	    v.isarr = v.inv = v.start = 0;
+	    v.isarr = v.flags = v.start = 0;
 	    v.end = -1;
 	    v.arr = NULL;
 	    v.pm = (Param) hn;
@@ -1325,7 +1325,7 @@ setpmnameddirs(UNUSED(Param pm), HashTable ht)
 	    struct value v;
 	    char *val;
 
-	    v.isarr = v.inv = v.start = 0;
+	    v.isarr = v.flags = v.start = 0;
 	    v.end = -1;
 	    v.arr = NULL;
 	    v.pm = (Param) hn;
@@ -1554,7 +1554,7 @@ setaliases(HashTable alht, UNUSED(Param pm), HashTable ht, int flags)
 	    struct value v;
 	    char *val;
 
-	    v.isarr = v.inv = v.start = 0;
+	    v.isarr = v.flags = v.start = 0;
 	    v.end = -1;
 	    v.arr = NULL;
 	    v.pm = (Param) hn;
diff --git a/Src/Zle/complete.c b/Src/Zle/complete.c
index 245f73126..a2af30c65 100644
--- a/Src/Zle/complete.c
+++ b/Src/Zle/complete.c
@@ -1139,7 +1139,7 @@ set_compstate(UNUSED(Param pm), HashTable ht)
 	    for (cp = compkparams,
 		 pp = compkpms; cp->name; cp++, pp++)
 		if (!strcmp(hn->nam, cp->name)) {
-		    v.isarr = v.inv = v.start = 0;
+		    v.isarr = v.flags = v.start = 0;
 		    v.end = -1;
 		    v.arr = NULL;
 		    v.pm = (Param) hn;
diff --git a/Src/glob.c b/Src/glob.c
index afd362c08..d95eced35 100644
--- a/Src/glob.c
+++ b/Src/glob.c
@@ -1470,7 +1470,7 @@ zglob(LinkList list, LinkNode np, int nountok)
 		    v.isarr = SCANPM_WANTVALS;
 		    v.pm = NULL;
 		    v.end = -1;
-		    v.inv = 0;
+		    v.flags = 0;
 		    if (getindex(&s, &v, 0) || s == os) {
 			zerr("invalid subscript");
 			restore_globstate(saved);
diff --git a/Src/options.c b/Src/options.c
index f3135759b..b46986495 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -153,9 +153,10 @@ static struct optname optns[] = {
 {{NULL, "interactivecomments",OPT_BOURNE},		 INTERACTIVECOMMENTS},
 {{NULL, "ksharrays",	      OPT_EMULATE|OPT_BOURNE},	 KSHARRAYS},
 {{NULL, "kshautoload",	      OPT_EMULATE|OPT_BOURNE},	 KSHAUTOLOAD},
-{{NULL, "kshglob",            OPT_EMULATE|OPT_KSH},      KSHGLOB},
+{{NULL, "kshglob",	      OPT_EMULATE|OPT_KSH},	 KSHGLOB},
 {{NULL, "kshoptionprint",     OPT_EMULATE|OPT_KSH},	 KSHOPTIONPRINT},
-{{NULL, "kshtypeset",         OPT_EMULATE|OPT_KSH},	 KSHTYPESET},
+{{NULL, "kshtypeset",	      OPT_EMULATE|OPT_KSH},	 KSHTYPESET},
+{{NULL, "kshzerosubscript",   0},			 KSHZEROSUBSCRIPT},
 {{NULL, "listambiguous",      OPT_ALL},			 LISTAMBIGUOUS},
 {{NULL, "listbeep",	      OPT_ALL},			 LISTBEEP},
 {{NULL, "listpacked",	      0},			 LISTPACKED},
diff --git a/Src/params.c b/Src/params.c
index a44f21402..5609437a2 100644
--- a/Src/params.c
+++ b/Src/params.c
@@ -520,7 +520,7 @@ scanparamvals(HashNode hn, int flags)
 	    return;
     }
     v.isarr = (PM_TYPE(v.pm->node.flags) & (PM_ARRAY|PM_HASHED));
-    v.inv = 0;
+    v.flags = 0;
     v.start = 0;
     v.end = -1;
     paramvals[numparamvals] = getstrvalue(&v);
@@ -1298,7 +1298,7 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w,
 		    (*ta || ((v->isarr & SCANPM_MATCHMANY) &&
 			     (v->isarr & (SCANPM_MATCHKEY | SCANPM_MATCHVAL |
 					  SCANPM_KEYMATCH))))) {
-		    *inv = v->inv;
+		    *inv = (v->flags & VALFLAG_INV) ? 1 : 0;
 		    *w = v->end;
 		    return 1;
 		}
@@ -1317,19 +1317,6 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w,
 			if (pprog && pattry(pprog, *p) && !--num)
 			    return r;
 		    }
-		    /*
-		     * Failed to match.
-		     * If we're returning an index, return 0 to show
-		     * we've gone off the start.  Unfortunately this
-		     * is ambiguous with KSH_ARRAYS set, but we're
-		     * stuck with that now.
-		     *
-		     * If the index is to be turned into an element,
-		     * return an index that does not point to a valid
-		     * element (since 0 is treated the same as 1).
-		     */
-		    if (!ind)
-			r = len + 1;
 		} else
 		    for (r = 1 + beg, p = ta + beg; *p; r++, p++)
 			if (pprog && pattry(pprog, *p) && !--num)
@@ -1549,13 +1536,7 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w,
 		    }
 		}
 	    }
-	    /*
-	     * Failed to match.
-	     * If the argument selects an element rather than
-	     * its index, ensure the element is empty.
-	     * See comments on the array case above.
-	     */
-	    return (down && ind) ? 0 : slen + 1;
+	    return down ? 0 : slen + 1;
 	}
     }
     return r;
@@ -1563,13 +1544,14 @@ getarg(char **str, int *inv, Value v, int a2, zlong *w,
 
 /**/
 int
-getindex(char **pptr, Value v, int dq)
+getindex(char **pptr, Value v, int flags)
 {
     int start, end, inv = 0;
     char *s = *pptr, *tbrack;
 
     *s++ = '[';
-    s = parse_subscript(s, dq);	/* Error handled after untokenizing */
+    /* Error handled after untokenizing */
+    s = parse_subscript(s, flags & SCANPM_DQUOTED);
     /* Now we untokenize everything except inull() markers so we can check *
      * for the '*' and '@' special subscripts.  The inull()s are removed  *
      * in getarg() after we know whether we're doing reverse indexing.    */
@@ -1654,7 +1636,7 @@ getindex(char **pptr, Value v, int dq)
 	    if (start > 0 && (isset(KSHARRAYS) || (v->pm->node.flags & PM_HASHED)))
 		start--;
 	    if (v->isarr != SCANPM_WANTINDEX) {
-		v->inv = 1;
+		v->flags |= VALFLAG_INV;
 		v->isarr = 0;
 		v->start = start;
 		v->end = start + 1;
@@ -1686,7 +1668,32 @@ getindex(char **pptr, Value v, int dq)
 	    if (start > 0)
 		start -= startprevlen;
 	    else if (start == 0 && end == 0)
-		end = startnextlen;
+	    {
+		/*
+		 * Strictly, this range is entirely off the
+		 * start of the available index range.
+		 * This can't happen with KSH_ARRAYS; we already
+		 * altered the start index in getarg().
+		 * Are we being strict?
+		 */
+		if (isset(KSHZEROSUBSCRIPT)) {
+		    /*
+		     * We're not.
+		     * Treat this as accessing the first element of the
+		     * array.
+		     */
+		    end = startnextlen;
+		} else {
+		    /*
+		     * We are.  Flag that this range is invalid
+		     * for setting elements.  Set the indexes
+		     * to a range that returns empty for other accesses.
+		     */
+		    v->flags |= VALFLAG_EMPTY;
+		    start = -1;
+		    com = 1;
+		}
+	    }
 	    if (s == tbrack) {
 		s++;
 		if (v->isarr && !com &&
@@ -1755,7 +1762,7 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
 	else
 	    v = (Value) hcalloc(sizeof *v);
 	v->pm = argvparam;
-	v->inv = 0;
+	v->flags = 0;
 	v->start = ppar - 1;
 	v->end = ppar;
 	if (sav)
@@ -1786,11 +1793,11 @@ fetchvalue(Value v, char **pptr, int bracks, int flags)
 		v->isarr = SCANPM_MATCHMANY;
 	}
 	v->pm = pm;
-	v->inv = 0;
+	v->flags = 0;
 	v->start = 0;
 	v->end = -1;
 	if (bracks > 0 && (*s == '[' || *s == Inbrack)) {
-	    if (getindex(&s, v, (flags & SCANPM_DQUOTED))) {
+	    if (getindex(&s, v, flags)) {
 		*pptr = s;
 		return v;
 	    }
@@ -1830,7 +1837,7 @@ getstrvalue(Value v)
     if (!v)
 	return hcalloc(1);
 
-    if (v->inv && !(v->pm->node.flags & PM_HASHED)) {
+    if ((v->flags & VALFLAG_INV) && !(v->pm->node.flags & PM_HASHED)) {
 	sprintf(buf, "%d", v->start);
 	s = dupstring(buf);
 	return s;
@@ -1911,7 +1918,7 @@ getarrvalue(Value v)
 	return arrdup(nular);
     else if (IS_UNSET_VALUE(v))
 	return arrdup(&nular[1]);
-    if (v->inv) {
+    if (v->flags & VALFLAG_INV) {
 	char buf[DIGBUFSIZE];
 
 	s = arrdup(nular);
@@ -1943,7 +1950,7 @@ getintvalue(Value v)
 {
     if (!v)
 	return 0;
-    if (v->inv)
+    if (v->flags & VALFLAG_INV)
 	return v->start;
     if (v->isarr) {
 	char **arr = getarrvalue(v);
@@ -1970,7 +1977,7 @@ getnumvalue(Value v)
 
     if (!v) {
 	mn.u.l = 0;
-    } else if (v->inv) {
+    } else if (v->flags & VALFLAG_INV) {
 	mn.u.l = v->start;
     } else if (v->isarr) {
 	char **arr = getarrvalue(v);
@@ -2000,7 +2007,7 @@ export_param(Param pm)
 	if (emulation == EMULATE_KSH /* isset(KSHARRAYS) */) {
 	    struct value v;
 	    v.isarr = 1;
-	    v.inv = 0;
+	    v.flags = 0;
 	    v.start = 0;
 	    v.end = -1;
 	    val = getstrvalue(&v);
@@ -2037,6 +2044,11 @@ setstrvalue(Value v, char *val)
 	zsfree(val);
 	return;
     }
+    if (v->flags & VALFLAG_EMPTY) {
+	zerr("%s: assignment to invalid subscript range", v->pm->node.nam);
+	zsfree(val);
+	return;
+    }
     v->pm->node.flags &= ~PM_UNSET;
     switch (PM_TYPE(v->pm->node.flags)) {
     case PM_SCALAR:
@@ -2051,7 +2063,7 @@ setstrvalue(Value v, char *val)
 
 	    z = dupstring(v->pm->gsu.s->getfn(v->pm));
 	    zlen = strlen(z);
-	    if (v->inv && unset(KSHARRAYS))
+	    if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS))
 		v->start--, v->end--;
 	    if (v->start < 0) {
 		v->start += zlen;
@@ -2176,6 +2188,11 @@ setarrvalue(Value v, char **val)
 	     v->pm->node.nam);
 	return;
     }
+    if (v->flags & VALFLAG_EMPTY) {
+	zerr("%s: assignment to invalid subscript range", v->pm->node.nam);
+	freearray(val);
+	return;
+    }
     if (v->start == 0 && v->end == -1) {
 	if (PM_TYPE(v->pm->node.flags) == PM_HASHED)
 	    arrhashsetfn(v->pm, val, 0);
@@ -2194,7 +2211,7 @@ setarrvalue(Value v, char **val)
 		 v->pm->node.nam);
 	    return;
 	}
-	if (v->inv && unset(KSHARRAYS)) {
+	if ((v->flags & VALFLAG_INV) && unset(KSHARRAYS)) {
 	    if (v->start > 0)
 		v->start--;
 	    v->end--;
diff --git a/Src/subst.c b/Src/subst.c
index 5d7a44b05..0cb8d48bf 100644
--- a/Src/subst.c
+++ b/Src/subst.c
@@ -2008,7 +2008,7 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 	    v->isarr = isarr;
 	    v->pm = pm;
 	    v->end = -1;
-	    if (getindex(&s, v, qt) || s == os)
+	    if (getindex(&s, v, qt ? SCANPM_DQUOTED : 0) || s == os)
 		break;
 	}
 	/*
@@ -2025,8 +2025,11 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 	 * in the subexp stuff or immediately above.
 	 */
 	if ((isarr = v->isarr)) {
-	    /* No way to get here with v->inv != 0, so getvaluearr() *
-	     * is called by getarrvalue(); needn't test PM_HASHED.   */
+	    /*
+	     * No way to get here with v->flags & VALFLAG_INV, so
+	     * getvaluearr() is called by getarrvalue(); needn't test
+	     * PM_HASHED.
+	     */
 	    if (v->isarr == SCANPM_WANTINDEX) {
 		isarr = v->isarr = 0;
 		val = dupstring(v->pm->node.nam);
@@ -2048,8 +2051,9 @@ paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
 		int tmplen = arrlen(v->pm->gsu.a->getfn(v->pm));
 
 		if (v->start < 0)
-		    v->start += tmplen + v->inv;
-		if (!v->inv && (v->start >= tmplen || v->start < 0))
+		    v->start += tmplen + ((v->flags & VALFLAG_INV) ? 1 : 0);
+		if (!(v->flags & VALFLAG_INV) &&
+		    (v->start >= tmplen || v->start < 0))
 		    vunset = 1;
 	    }
 	    if (!vunset) {
diff --git a/Src/zsh.h b/Src/zsh.h
index f7255c6e7..4f11b19ad 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -585,12 +585,17 @@ struct asgment {
 struct value {
     int isarr;
     Param pm;		/* parameter node                      */
-    int inv;		/* should we return the index ?        */
+    int flags;		/* flags defined below                 */
     int start;		/* first element of array slice, or -1 */
     int end;		/* 1-rel last element of array slice, or -1 */
     char **arr;		/* cache for hash turned into array */
 };
 
+enum {
+    VALFLAG_INV =	0x0001,	/* We are performing inverse subscripting */
+    VALFLAG_EMPTY =	0x0002	/* Subscripted range is empty */
+};
+
 #define MAX_ARRLEN    262144
 
 /********************************************/
@@ -1725,6 +1730,7 @@ enum {
     KSHGLOB,
     KSHOPTIONPRINT,
     KSHTYPESET,
+    KSHZEROSUBSCRIPT,
     LISTAMBIGUOUS,
     LISTBEEP,
     LISTPACKED,
diff --git a/Test/C01arith.ztst b/Test/C01arith.ztst
index e4ddebc12..0e9216aaf 100644
--- a/Test/C01arith.ztst
+++ b/Test/C01arith.ztst
@@ -91,8 +91,13 @@
 >3.5
 >4
 
-  (( newarray[unsetvar]++ ))
-  (( newarray[unsetvar]++ ))
+  (( newarray[unsetvar] = 1 ))
+2:error using unset variable as index
+?(eval):1:  assignment to invalid subscript range
+
+  integer setvar=1
+  (( newarray[setvar]++ ))
+  (( newarray[setvar]++ ))
   print ${(t)newarray} ${#newarray} ${newarray[1]}
 0:setting array elements in math context
 >array 1 2
diff --git a/Test/D05array.ztst b/Test/D05array.ztst
index 5ca83ee6d..1fa607ddc 100644
--- a/Test/D05array.ztst
+++ b/Test/D05array.ztst
@@ -30,12 +30,12 @@
 >..
 
   echo .$foo[0].
-0:Treat 0 like 1
->.a.
+0:Treat 0 as empty
+>..
 
   echo .$foo[0,0].
-0:Treat 0,0 like 1,1.
->.a.
+0:Treat 0,0 as empty
+>..
 
   echo .$foo[0,1].
 0:Another weird way to access the first element
diff --git a/Test/D06subscript.ztst b/Test/D06subscript.ztst
index 4dd8a8237..6c9f477cc 100644
--- a/Test/D06subscript.ztst
+++ b/Test/D06subscript.ztst
@@ -182,3 +182,56 @@
   echo X${${l##*}[-1]}X
 0:Negative index applied to substition result from empty array
 >XX
+
+  array=(one two three four)
+  print X$array[0]X
+0:Element zero is empty if KSH_ZERO_SUBSCRIPT is off.
+>XX
+
+  array[0]=fumble
+1:Can't set element zero if KSH_ZERO_SUBSCRIPT is off.
+?(eval):1: array: assignment to invalid subscript range
+
+  print X$array[(R)notfound]X
+0:(R) returns empty if not found if KSH_ZERO_SUBSCRIPT is off.
+>XX
+
+  setopt KSH_ZERO_SUBSCRIPT
+  print X$array[0]X
+0:Element zero is element one if KSH_ZERO_SUBSCRIPT is on.
+>XoneX
+
+  array[0]=fimble
+  print $array
+0:Can set element zero if KSH_ZERO_SUBSCRIPT is on.
+>fimble two three four
+
+  print X$array[(R)notfound]X
+0:(R) yuckily returns the first element on failure withe KSH_ZERO_SUBSCRIPT
+>XfimbleX
+
+  unsetopt KSH_ZERO_SUBSCRIPT
+  array[(R)notfound,(r)notfound]=(help help here come the seventies retreads)
+  print $array
+0:[(R)notfound,(r)notfound] replaces the whole array
+>help help here come the seventies retreads
+
+  string="Why, if it isn't Officer Dibble"
+  print "[${string[0]}][${string[1]}][${string[0,3]}]"
+0:String subscripts with KSH_ZERO_SUBSCRIPT unset
+>[][W][Why]
+
+  setopt KSH_ZERO_SUBSCRIPT
+  print "[${string[0]}][${string[1]}][${string[0,3]}]"
+0:String subscripts with KSH_ZERO_SUBSCRIPT set
+>[W][W][Why]
+
+  unsetopt KSH_ZERO_SUBSCRIPT
+  string[0,3]="Goodness"
+  print $string
+0:Assignment to chunk of string ignores element 0
+>Goodness, if it isn't Officer Dibble
+
+  string[0]=!
+1:Can't set only element zero of string
+?(eval):1: string: assignment to invalid subscript range
diff --git a/Test/D07multibyte.ztst b/Test/D07multibyte.ztst
index 765559ae5..35e2bd50f 100644
--- a/Test/D07multibyte.ztst
+++ b/Test/D07multibyte.ztst
@@ -88,7 +88,7 @@
   s=é
   print A${s[-2]}A B${s[-1]}B C${s[0]}C D${s[1]}D E${s[2]}E
 0:Out of range subscripts with multibyte characters
->AA BéB CéC DéD EE
+>AA BéB CC DéD EE
 
   print ${a[(i)é]} ${a[(I)é]} ${a[${a[(i)é]},${a[(I)é]}]}
 0:Reverse indexing with multibyte characters
diff --git a/Test/E01options.ztst b/Test/E01options.ztst
index f8ab297c0..66bfa1955 100644
--- a/Test/E01options.ztst
+++ b/Test/E01options.ztst
@@ -521,7 +521,7 @@
 >one one[2]
 >one two three
 >one two three two
->one one two three
+>one two three
 
   fpath=(.)
   echo >foo 'echo foo loaded; foo() { echo foo run; }'