This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Reimplement $[ as a module
authorFather Chrysostomos <sprout@cpan.org>
Fri, 21 Oct 2011 12:58:40 +0000 (05:58 -0700)
committerFather Chrysostomos <sprout@cpan.org>
Sat, 22 Oct 2011 05:12:59 +0000 (22:12 -0700)
This commit reimplements $[ using PL_check hooks, custom pp func-
tions and ties.

Outside of its compile-time use, $[ is now parsed as a simple varia-
ble, so function calls like foo($[) are permitted, which was not the
case with the former implementation removed by e1dccc0.  I consider
that a bug fix.

The ‘That use of $[ is unsupported’ errors are out of necessity
deferred to run-time and implemented by a tied $[.

Indices between 0 and the array base are now treated consistently, as
are indices between a negative array base and zero.  That, too, is
a bug fix.

26 files changed:
MANIFEST
Porting/Maintainers.pl
ext/arybase/Makefile.PL [new file with mode: 0644]
ext/arybase/arybase.pm [new file with mode: 0644]
ext/arybase/arybase.xs [new file with mode: 0644]
ext/arybase/ptable.h [new file with mode: 0644]
ext/arybase/t/aeach.t [new file with mode: 0644]
ext/arybase/t/aelem.t [new file with mode: 0644]
ext/arybase/t/akeys.t [new file with mode: 0644]
ext/arybase/t/arybase.t [new file with mode: 0644]
ext/arybase/t/aslice.t [new file with mode: 0644]
ext/arybase/t/av2arylen.t [new file with mode: 0644]
ext/arybase/t/index.t [new file with mode: 0644]
ext/arybase/t/lslice.t [new file with mode: 0644]
ext/arybase/t/pos.t [new file with mode: 0644]
ext/arybase/t/scope.t [new file with mode: 0644]
ext/arybase/t/scope_0.pm [new file with mode: 0644]
ext/arybase/t/splice.t [new file with mode: 0644]
ext/arybase/t/substr.t [new file with mode: 0644]
gv.c
mg.c
pod/perldiag.pod
pod/perlvar.pod
t/op/array_base.t
t/op/magic.t
t/porting/known_pod_issues.dat

index e5a0da1..1309baa 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -3507,6 +3507,23 @@ epoc/epocish.c           EPOC port
 epoc/epocish.h         EPOC port
 epoc/epoc_stubs.c      EPOC port
 epoc/link.pl           EPOC port link a exe
+ext/arybase/arybase.pm         For $[
+ext/arybase/arybase.xs         For $[
+ext/arybase/Makefile.PL                For $[
+ext/arybase/ptable.h           For $[
+ext/arybase/t/aeach.t          For $[
+ext/arybase/t/aelem.t          For $[
+ext/arybase/t/akeys.t          For $[
+ext/arybase/t/arybase.t                For $[
+ext/arybase/t/aslice.t         For $[
+ext/arybase/t/av2arylen.t      For $[
+ext/arybase/t/index.t          For $[
+ext/arybase/t/lslice.t         For $[
+ext/arybase/t/pos.t            For $[
+ext/arybase/t/scope_0.pm       For $[
+ext/arybase/t/scope.t          For $[
+ext/arybase/t/splice.t         For $[
+ext/arybase/t/substr.t         For $[
 ext/attributes/attributes.pm           For "sub foo : attrlist"
 ext/attributes/attributes.xs           For "sub foo : attrlist"
 ext/B/B/Concise.pm     Compiler Concise backend
index 52bc502..f8d655a 100755 (executable)
@@ -2120,6 +2120,7 @@ use File::Glob qw(:case);
        {
        'MAINTAINER'    => 'p5p',
        'FILES'         => q[
+                               ext/arybase/
                                ext/XS-APItest/
                                lib/CORE.pod
                                lib/Config.t
diff --git a/ext/arybase/Makefile.PL b/ext/arybase/Makefile.PL
new file mode 100644 (file)
index 0000000..2d372a6
--- /dev/null
@@ -0,0 +1,16 @@
+use ExtUtils::MakeMaker;
+
+WriteMakefile(
+    NAME          => 'arybase',
+    VERSION_FROM  => 'arybase.pm',
+    ABSTRACT_FROM => 'arybase.pm',
+    realclean     => { FILES => "" },
+);
+
+# To work around nmake stupidity.  See rt.cpan.org #71847.
+package MY;
+sub ppd {
+ my $stuff = SUPER::ppd{} @_;
+ $stuff =~ s/ \$\[/ \$\$[/;
+ $stuff;
+}
diff --git a/ext/arybase/arybase.pm b/ext/arybase/arybase.pm
new file mode 100644 (file)
index 0000000..829f2db
--- /dev/null
@@ -0,0 +1,98 @@
+package arybase;
+
+our $VERSION = "0.01";
+
+require XSLoader;
+XSLoader::load(); # This returns true, which makes require happy.
+
+__END__
+
+=head1 NAME
+
+arybase - Set indexing base via $[
+
+=head1 SYNOPSIS
+
+    $[ = 1;
+    
+    @a = qw(Sun Mon Tue Wed Thu Fri Sat);
+    print $a[3], "\n";  # prints Tue
+
+=head1 DESCRIPTION
+
+This module implements Perl's C<$[> variable.  You should not use it
+directly.
+
+Assigning to C<$[> has the I<compile-time> effect of making the assigned
+value, converted to an integer, the index of the first element in an array
+and the first character in a substring, within the enclosing lexical scope.
+
+It can be written with or without C<local>:
+
+    $[ = 1;
+    local $[ = 1;
+
+It only works if the assignment can be detected at compile time and the
+value assigned is constant.
+
+It affects the following operations:
+
+    $array[$element]
+    @array[@slice]
+    $#array
+    (list())[$slice]
+    splice @array, $index, ...
+    each @array
+    keys @array
+    
+    index $string, $substring  # return value is affected
+    pos $string
+    substr $string, $offset, ...
+
+As with the default base of 0, negative bases count from the end of the
+array or string, starting with -1.  If C<$[> is a positive integer, indices
+from C<$[-1> to 0 also count from the end.  If C<$[> is negative (why would
+you do that, though?), indices from C<$[> to 0 count from the beginning of
+the string, but indices below C<$[> count from the end of the string as
+though the base were 0.
+
+Prior to Perl 5.16, indices from 0 to C<$[-1> inclusive, for positive
+values of C<$[>, behaved differently for different operations; negative
+indices equal to or greater than a negative C<$[> likewise behaved
+inconsistently.
+
+=head1 HISTORY
+
+Before Perl 5, C<$[> was a global variable that affected all array indices
+and string offsets.
+
+Starting with Perl 5, it became a file-scoped compile-time directive, which
+could be made lexically-scoped with C<local>.  "File-scoped" means that the
+C<$[> assignment could leak out of the block in which occurred:
+
+    {
+        $[ = 1;
+        # ... array base is 1 here ...
+    }
+    # ... still 1, but not in other files ...
+
+In Perl 5.10, it became strictly lexical.  The file-scoped behaviour was
+removed (perhaps inadvertently, but what's done is done).
+
+In Perl 5.16, the implementation was moved into this module, and out of the
+Perl core.  The erratic behaviour that occurred with indices between -1 and
+C<$[> was made consistent between operations, and, for negative bases,
+indices from C<$[> to -1 inclusive were made consistent between operations.
+
+=head1 BUGS
+
+Error messages that mention array indices use the 0-based index.
+
+C<keys $arrayref> and C<each $arrayref> do not respect the current value of
+C<$[>.
+
+=head1 SEE ALSO
+
+L<perlvar/"$[">, L<Array::Base> and L<String::Base>.
+
+=cut
diff --git a/ext/arybase/arybase.xs b/ext/arybase/arybase.xs
new file mode 100644 (file)
index 0000000..3151d31
--- /dev/null
@@ -0,0 +1,460 @@
+#define PERL_NO_GET_CONTEXT     /* we want efficiency */
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+
+/* ... op => info map ................................................. */
+
+typedef struct {
+ OP *(*old_pp)(pTHX);
+ IV base;
+} ab_op_info;
+
+#define PTABLE_NAME        ptable_map
+#define PTABLE_VAL_FREE(V) PerlMemShared_free(V)
+#include "ptable.h"
+#define ptable_map_store(T, K, V) ptable_map_store(aPTBLMS_ (T), (K), (V))
+
+STATIC ptable *ab_op_map = NULL;
+
+#ifdef USE_ITHREADS
+STATIC perl_mutex ab_op_map_mutex;
+#endif
+
+STATIC const ab_op_info *ab_map_fetch(const OP *o, ab_op_info *oi) {
+ const ab_op_info *val;
+
+#ifdef USE_ITHREADS
+ MUTEX_LOCK(&ab_op_map_mutex);
+#endif
+
+ val = ptable_fetch(ab_op_map, o);
+ if (val) {
+  *oi = *val;
+  val = oi;
+ }
+
+#ifdef USE_ITHREADS
+ MUTEX_UNLOCK(&ab_op_map_mutex);
+#endif
+
+ return val;
+}
+
+STATIC const ab_op_info *ab_map_store_locked(
+ pPTBLMS_ const OP *o, OP *(*old_pp)(pTHX), IV base
+) {
+#define ab_map_store_locked(O, PP, B) \
+  ab_map_store_locked(aPTBLMS_ (O), (PP), (B))
+ ab_op_info *oi;
+
+ if (!(oi = ptable_fetch(ab_op_map, o))) {
+  oi = PerlMemShared_malloc(sizeof *oi);
+  ptable_map_store(ab_op_map, o, oi);
+ }
+
+ oi->old_pp = old_pp;
+ oi->base   = base;
+ return oi;
+}
+
+STATIC void ab_map_store(
+ pPTBLMS_ const OP *o, OP *(*old_pp)(pTHX), IV base)
+{
+#define ab_map_store(O, PP, B) ab_map_store(aPTBLMS_ (O),(PP),(B))
+
+#ifdef USE_ITHREADS
+ MUTEX_LOCK(&ab_op_map_mutex);
+#endif
+
+ ab_map_store_locked(o, old_pp, base);
+
+#ifdef USE_ITHREADS
+ MUTEX_UNLOCK(&ab_op_map_mutex);
+#endif
+}
+
+STATIC void ab_map_delete(pTHX_ const OP *o) {
+#define ab_map_delete(O) ab_map_delete(aTHX_ (O))
+#ifdef USE_ITHREADS
+ MUTEX_LOCK(&ab_op_map_mutex);
+#endif
+
+ ptable_map_store(ab_op_map, o, NULL);
+
+#ifdef USE_ITHREADS
+ MUTEX_UNLOCK(&ab_op_map_mutex);
+#endif
+}
+
+/* ... $[ Implementation .............................................. */
+
+#define hintkey     "$["
+#define hintkey_len  (sizeof(hintkey)-1)
+
+STATIC SV * ab_hint(pTHX_ const bool create) {
+#define ab_hint(c) ab_hint(aTHX_ c)
+ dVAR;
+ SV **val
+  = hv_fetch(GvHV(PL_hintgv), hintkey, hintkey_len, create);
+ if (!val)
+  return 0;
+ return *val;
+}
+
+STATIC IV current_base(pTHX) {
+#define current_base() current_base(aTHX)
+ SV *hsv = ab_hint(0);
+ if (!hsv || !SvOK(hsv)) return 0;
+ return SvIV(hsv);
+}
+
+STATIC void set_arybase_to(pTHX_ IV base) {
+#define set_arybase_to(base) set_arybase_to(aTHX_ (base))
+ dVAR;
+ SV *hsv = ab_hint(1);
+ sv_setiv_mg(hsv, base);
+}
+
+#define old_ck(opname) STATIC OP *(*ab_old_ck_##opname)(pTHX_ OP *) = 0;
+old_ck(sassign);
+old_ck(aassign);
+old_ck(aelem);
+old_ck(aslice);
+old_ck(lslice);
+old_ck(av2arylen);
+old_ck(splice);
+old_ck(keys);
+old_ck(each);
+old_ck(substr);
+old_ck(rindex);
+old_ck(index);
+old_ck(pos);
+
+STATIC bool ab_op_is_dollar_bracket(pTHX_ OP *o) {
+#define ab_op_is_dollar_bracket(o) ab_op_is_dollar_bracket(aTHX_ (o))
+ OP *c;
+ return o->op_type == OP_RV2SV && (o->op_flags & OPf_KIDS)
+  && (c = cUNOPx(o)->op_first)
+  && c->op_type == OP_GV
+  && strEQ(GvNAME(cGVOPx_gv(c)), "[");
+}
+
+STATIC void ab_neuter_dollar_bracket(pTHX_ OP *o) {
+#define ab_neuter_dollar_bracket(o) ab_neuter_dollar_bracket(aTHX_ (o))
+ OP *oldc, *newc;
+ /*
+  * Must replace the core's $[ with something that can accept assignment
+  * of non-zero value and can be local()ised.  Simplest thing is a
+  * different global variable.
+  */
+ oldc = cUNOPx(o)->op_first;
+ newc = newGVOP(OP_GV, 0,
+   gv_fetchpvs("arybase::[", GV_ADDMULTI, SVt_PVGV));
+ cUNOPx(o)->op_first = newc;
+ op_free(oldc);
+}
+
+STATIC void ab_process_assignment(pTHX_ OP *left, OP *right) {
+#define ab_process_assignment(l, r) \
+    ab_process_assignment(aTHX_ (l), (r))
+ if (ab_op_is_dollar_bracket(left) && right->op_type == OP_CONST) {
+  set_arybase_to(SvIV(cSVOPx_sv(right)));
+  ab_neuter_dollar_bracket(left);
+ }
+}
+
+STATIC OP *ab_ck_sassign(pTHX_ OP *o) {
+ o = (*ab_old_ck_sassign)(aTHX_ o);
+ {
+  OP *right = cBINOPx(o)->op_first;
+  OP *left = right->op_sibling;
+  if (left) ab_process_assignment(left, right);
+  return o;
+ }
+}
+
+STATIC OP *ab_ck_aassign(pTHX_ OP *o) {
+ o = (*ab_old_ck_aassign)(aTHX_ o);
+ {
+  OP *right = cBINOPx(o)->op_first;
+  OP *left = cBINOPx(right->op_sibling)->op_first->op_sibling;
+  right = cBINOPx(right)->op_first->op_sibling;
+  ab_process_assignment(left, right);
+  return o;
+ }
+}
+
+void
+tie(pTHX_ SV * const sv, SV * const obj, HV *const stash)
+{
+    SV *rv = newSV_type(SVt_RV);
+
+    SvRV_set(rv, obj ? SvREFCNT_inc_simple_NN(obj) : newSV(0));
+    SvROK_on(rv);
+    sv_bless(rv, stash);
+
+    sv_unmagic((SV *)sv, PERL_MAGIC_tiedscalar);
+    sv_magic((SV *)sv, rv, PERL_MAGIC_tiedscalar, NULL, 0);
+    SvREFCNT_dec(rv); /* As sv_magic increased it by one.  */
+}
+
+/* This function converts from base-based to 0-based an index to be passed
+   as an argument. */
+static IV
+adjust_index(IV index, IV base)
+{
+ if (index >= base || index > -1) return index-base;
+ return index;
+}
+/* This function converts from 0-based to base-based an index to
+   be returned. */
+static IV
+adjust_index_r(IV index, IV base)
+{
+ return index + base;
+}
+
+#define replace_sv(sv,base) \
+ ((sv) = sv_2mortal(newSViv(adjust_index(SvIV(sv),base))))
+#define replace_sv_r(sv,base) \
+ ((sv) = sv_2mortal(newSViv(adjust_index_r(SvIV(sv),base))))
+
+static OP *ab_pp_basearg(pTHX) {
+ dVAR; dSP;
+ SV **firstp = NULL;
+ SV **svp;
+ UV count = 1;
+ ab_op_info oi;
+ ab_map_fetch(PL_op, &oi);
+ switch (PL_op->op_type) {
+ case OP_AELEM:
+  firstp = SP;
+  break;
+ case OP_ASLICE:
+  firstp = PL_stack_base + TOPMARK + 1;
+  count = SP-firstp;
+  break;
+ case OP_LSLICE:
+  firstp = PL_stack_base + *(PL_markstack_ptr-2)+1;
+  count = TOPMARK - *(PL_markstack_ptr-2);
+  if (GIMME != G_ARRAY) {
+   firstp += count-1;
+   count = 1;
+  }
+  break;
+ case OP_SPLICE:
+  if (SP - PL_stack_base - TOPMARK >= 2)
+   firstp = PL_stack_base + TOPMARK + 2;
+  else count = 0;
+  break;
+ case OP_SUBSTR:
+  firstp = SP-(PL_op->op_private & 7)+2;
+  break;
+ default:
+  DIE(aTHX_
+     "panic: invalid op type for arybase.xs:ab_pp_basearg: %d",
+      PL_op->op_type);
+ }
+ svp = firstp;
+ while (count--) replace_sv(*svp,oi.base), svp++;
+ return (*oi.old_pp)(aTHX);
+}
+
+static OP *ab_pp_av2arylen(pTHX) {
+ dSP; dVAR;
+ SV *sv;
+ ab_op_info oi;
+ OP *ret;
+ ab_map_fetch(PL_op, &oi);
+ ret = (*oi.old_pp)(aTHX);
+ if (PL_op->op_flags & OPf_MOD || LVRET) {
+  sv = newSV(0);
+  tie(aTHX_ sv, TOPs, gv_stashpv("arybase::mg",1));
+  SETs(sv);
+ }
+ else {
+  SvGETMAGIC(TOPs);
+  if (SvOK(TOPs)) replace_sv_r(TOPs, oi.base);
+ }
+ return ret;
+}
+
+static OP *ab_pp_keys(pTHX) {
+ dVAR; dSP;
+ ab_op_info oi;
+ OP *retval;
+ const I32 offset = SP - PL_stack_base;
+ SV **svp;
+ ab_map_fetch(PL_op, &oi);
+ retval = (*oi.old_pp)(aTHX);
+ if (GIMME_V == G_SCALAR) return retval;
+ SPAGAIN;
+ svp = PL_stack_base + offset;
+ while (svp <= SP) replace_sv_r(*svp,oi.base), ++svp;
+ return retval; 
+}
+
+static OP *ab_pp_each(pTHX) {
+ dVAR; dSP;
+ ab_op_info oi;
+ OP *retval;
+ const I32 offset = SP - PL_stack_base;
+ ab_map_fetch(PL_op, &oi);
+ retval = (*oi.old_pp)(aTHX);
+ SPAGAIN;
+ if (GIMME_V == G_SCALAR) {
+  if (SvOK(TOPs)) replace_sv_r(TOPs,oi.base);
+ }
+ else if (offset < SP - PL_stack_base) replace_sv_r(TOPm1s,oi.base);
+ return retval; 
+}
+
+static OP *ab_pp_index(pTHX) {
+ dVAR; dSP;
+ ab_op_info oi;
+ OP *retval;
+ ab_map_fetch(PL_op, &oi);
+ if (MAXARG == 3 && TOPs) replace_sv(TOPs,oi.base);
+ retval = (*oi.old_pp)(aTHX);
+ SPAGAIN;
+ replace_sv_r(TOPs,oi.base);
+ return retval; 
+}
+
+static OP *ab_ck_base(pTHX_ OP *o)
+{
+ OP * (*old_ck)(pTHX_ OP *o) = 0;
+ OP * (*new_pp)(pTHX)        = ab_pp_basearg;
+ switch (o->op_type) {
+ case OP_AELEM    : old_ck = ab_old_ck_aelem    ; break;
+ case OP_ASLICE   : old_ck = ab_old_ck_aslice   ; break;
+ case OP_LSLICE   : old_ck = ab_old_ck_lslice   ; break;
+ case OP_AV2ARYLEN: old_ck = ab_old_ck_av2arylen; break;
+ case OP_SPLICE   : old_ck = ab_old_ck_splice   ; break;
+ case OP_KEYS     : old_ck = ab_old_ck_keys     ; break;
+ case OP_EACH     : old_ck = ab_old_ck_each     ; break;
+ case OP_SUBSTR   : old_ck = ab_old_ck_substr   ; break;
+ case OP_RINDEX   : old_ck = ab_old_ck_rindex   ; break;
+ case OP_INDEX    : old_ck = ab_old_ck_index    ; break;
+ case OP_POS      : old_ck = ab_old_ck_pos      ; break;
+ }
+ o = (*old_ck)(aTHX_ o);
+ /* We need two switch blocks, as the type may have changed. */
+ switch (o->op_type) {
+ case OP_AELEM    :
+ case OP_ASLICE   :
+ case OP_LSLICE   :
+ case OP_SPLICE   :
+ case OP_SUBSTR   : break;
+ case OP_POS      :
+ case OP_AV2ARYLEN: new_pp = ab_pp_av2arylen    ; break;
+ case OP_AKEYS    : new_pp = ab_pp_keys         ; break;
+ case OP_AEACH    : new_pp = ab_pp_each         ; break;
+ case OP_RINDEX   :
+ case OP_INDEX    : new_pp = ab_pp_index        ; break;
+ default: return o;
+ }
+ {
+  IV const base = current_base();
+  if (base) {
+   ab_map_store(o, o->op_ppaddr, base);
+   o->op_ppaddr = new_pp;
+   /* Break the aelemfast optimisation */
+   if (o->op_type == OP_AELEM &&
+       cBINOPo->op_first->op_sibling->op_type == OP_CONST) {
+     cBINOPo->op_first->op_sibling
+      = newUNOP(OP_NULL,0,cBINOPo->op_first->op_sibling);
+   }
+  }
+  else ab_map_delete(o);
+ }
+ return o;
+}
+
+
+STATIC U32 ab_initialized = 0;
+
+/* --- XS ------------------------------------------------------------- */
+
+MODULE = arybase       PACKAGE = arybase
+PROTOTYPES: DISABLE
+
+BOOT:
+{
+    GV *const gv = gv_fetchpvn("[", 1, GV_ADDMULTI|GV_NOTQUAL, SVt_PV);
+    tie(aTHX_ GvSV(gv), NULL, GvSTASH(CvGV(cv)));
+
+    if (!ab_initialized++) {
+       ab_op_map = ptable_new();
+#ifdef USE_ITHREADS
+       MUTEX_INIT(&ab_op_map_mutex);
+#endif
+#define check(uc,lc,ck) ab_old_ck_##lc = PL_check[OP_##uc]; \
+                       PL_check[OP_##uc] = ab_ck_##ck
+       check(SASSIGN,  sassign,  sassign);
+       check(AASSIGN,  aassign,  aassign);
+       check(AELEM,    aelem,    base);
+       check(ASLICE,   aslice,   base);
+       check(LSLICE,   lslice,   base);
+       check(AV2ARYLEN,av2arylen,base);
+       check(SPLICE,   splice,   base);
+       check(KEYS,     keys,     base);
+       check(EACH,     each,     base);
+       check(SUBSTR,   substr,   base);
+       check(RINDEX,   rindex,   base);
+       check(INDEX,    index,    base);
+       check(POS,      pos,      base);
+    }
+}
+
+void
+FETCH(...)
+    PREINIT:
+       SV *ret = cop_hints_fetch_pvs(PL_curcop, "$[", 0);
+    PPCODE:
+       if (!SvOK(ret)) mXPUSHi(0);
+       else XPUSHs(ret);
+
+void
+STORE(SV *sv, IV newbase)
+    PREINIT:
+       SV *base = cop_hints_fetch_pvs(PL_curcop, "$[", 0);
+    CODE:
+       if (SvOK(base) ? SvIV(base) == newbase : !newbase) XSRETURN_EMPTY;
+       Perl_croak(aTHX_ "That use of $[ is unsupported");
+
+
+MODULE = arybase       PACKAGE = arybase::mg
+PROTOTYPES: DISABLE
+
+void
+FETCH(SV *sv)
+    PPCODE:
+       if (!SvROK(sv) || SvTYPE(SvRV(sv)) >= SVt_PVAV)
+           Perl_croak(aTHX_ "Not a SCALAR reference");
+       {
+           SV *base = cop_hints_fetch_pvs(PL_curcop, "$[", 0);
+           SvGETMAGIC(SvRV(sv));
+           if (!SvOK(SvRV(sv))) XSRETURN_UNDEF;
+           mXPUSHi(adjust_index_r(
+               SvIV_nomg(SvRV(sv)), SvOK(base)?SvIV(base):0
+           ));
+       }
+
+void
+STORE(SV *sv, SV *newbase)
+    CODE:
+       if (!SvROK(sv) || SvTYPE(SvRV(sv)) >= SVt_PVAV)
+           Perl_croak(aTHX_ "Not a SCALAR reference");
+       {
+           SV *base = cop_hints_fetch_pvs(PL_curcop, "$[", 0);
+           SvGETMAGIC(newbase);
+           if (!SvOK(newbase)) SvSetMagicSV(SvRV(sv),&PL_sv_undef);
+           else 
+               sv_setiv_mg(
+                  SvRV(sv),
+                  adjust_index(SvIV_nomg(newbase),SvOK(base)?SvIV(base):0)
+               );
+       }
diff --git a/ext/arybase/ptable.h b/ext/arybase/ptable.h
new file mode 100644 (file)
index 0000000..e492e2f
--- /dev/null
@@ -0,0 +1,217 @@
+/* This is a pointer table implementation essentially copied from the ptr_table
+ * implementation in perl's sv.c, except that it has been modified to use memory
+ * shared across threads. */
+
+/* This header is designed to be included several times with different
+ * definitions for PTABLE_NAME and PTABLE_VAL_FREE(). */
+
+#undef pPTBLMS
+#undef pPTBLMS_
+#undef aPTBLMS
+#undef aPTBLMS_
+
+/* Context for PerlMemShared_* functions */
+
+#ifdef PERL_IMPLICIT_SYS
+# define pPTBLMS  pTHX
+# define pPTBLMS_ pTHX_
+# define aPTBLMS  aTHX
+# define aPTBLMS_ aTHX_
+#else
+# define pPTBLMS
+# define pPTBLMS_
+# define aPTBLMS
+# define aPTBLMS_
+#endif
+
+#ifndef pPTBL
+# define pPTBL  pPTBLMS
+#endif
+#ifndef pPTBL_
+# define pPTBL_ pPTBLMS_
+#endif
+#ifndef aPTBL
+# define aPTBL  aPTBLMS
+#endif
+#ifndef aPTBL_
+# define aPTBL_ aPTBLMS_
+#endif
+
+#ifndef PTABLE_NAME
+# define PTABLE_NAME ptable
+#endif
+
+#ifndef PTABLE_VAL_FREE
+# define PTABLE_VAL_FREE(V)
+#endif
+
+#ifndef PTABLE_JOIN
+# define PTABLE_PASTE(A, B) A ## B
+# define PTABLE_JOIN(A, B)  PTABLE_PASTE(A, B)
+#endif
+
+#ifndef PTABLE_PREFIX
+# define PTABLE_PREFIX(X) PTABLE_JOIN(PTABLE_NAME, X)
+#endif
+
+#ifndef ptable_ent
+typedef struct ptable_ent {
+ struct ptable_ent *next;
+ const void *       key;
+ void *             val;
+} ptable_ent;
+#define ptable_ent ptable_ent
+#endif /* !ptable_ent */
+
+#ifndef ptable
+typedef struct ptable {
+ ptable_ent **ary;
+ UV           max;
+ UV           items;
+} ptable;
+#define ptable ptable
+#endif /* !ptable */
+
+#ifndef ptable_new
+STATIC ptable *ptable_new(pPTBLMS) {
+#define ptable_new() ptable_new(aPTBLMS)
+ ptable *t = PerlMemShared_malloc(sizeof *t);
+ t->max   = 63;
+ t->items = 0;
+ t->ary   = PerlMemShared_calloc(t->max + 1, sizeof *t->ary);
+ return t;
+}
+#endif /* !ptable_new */
+
+#ifndef PTABLE_HASH
+# define PTABLE_HASH(ptr) \
+     ((PTR2UV(ptr) >> 3) ^ (PTR2UV(ptr) >> (3 + 7)) ^ (PTR2UV(ptr) >> (3 + 17)))
+#endif
+
+#ifndef ptable_find
+STATIC ptable_ent *ptable_find(const ptable * const t, const void * const key) {
+#define ptable_find ptable_find
+ ptable_ent *ent;
+ const UV hash = PTABLE_HASH(key);
+
+ ent = t->ary[hash & t->max];
+ for (; ent; ent = ent->next) {
+  if (ent->key == key)
+   return ent;
+ }
+
+ return NULL;
+}
+#endif /* !ptable_find */
+
+#ifndef ptable_fetch
+STATIC void *ptable_fetch(const ptable * const t, const void * const key) {
+#define ptable_fetch ptable_fetch
+ const ptable_ent *const ent = ptable_find(t, key);
+
+ return ent ? ent->val : NULL;
+}
+#endif /* !ptable_fetch */
+
+#ifndef ptable_split
+STATIC void ptable_split(pPTBLMS_ ptable * const t) {
+#define ptable_split(T) ptable_split(aPTBLMS_ (T))
+ ptable_ent **ary = t->ary;
+ const UV oldsize = t->max + 1;
+ UV newsize = oldsize * 2;
+ UV i;
+
+ ary = PerlMemShared_realloc(ary, newsize * sizeof(*ary));
+ Zero(&ary[oldsize], newsize - oldsize, sizeof(*ary));
+ t->max = --newsize;
+ t->ary = ary;
+
+ for (i = 0; i < oldsize; i++, ary++) {
+  ptable_ent **curentp, **entp, *ent;
+  if (!*ary)
+   continue;
+  curentp = ary + oldsize;
+  for (entp = ary, ent = *ary; ent; ent = *entp) {
+   if ((newsize & PTABLE_HASH(ent->key)) != i) {
+    *entp     = ent->next;
+    ent->next = *curentp;
+    *curentp  = ent;
+    continue;
+   } else
+    entp = &ent->next;
+  }
+ }
+}
+#endif /* !ptable_split */
+
+STATIC void PTABLE_PREFIX(_store)(pPTBL_ ptable * const t, const void * const key, void * const val) {
+ ptable_ent *ent = ptable_find(t, key);
+
+ if (ent) {
+  void *oldval = ent->val;
+  PTABLE_VAL_FREE(oldval);
+  ent->val = val;
+ } else if (val) {
+  const UV i = PTABLE_HASH(key) & t->max;
+  ent = PerlMemShared_malloc(sizeof *ent);
+  ent->key  = key;
+  ent->val  = val;
+  ent->next = t->ary[i];
+  t->ary[i] = ent;
+  t->items++;
+  if (ent->next && t->items > t->max)
+   ptable_split(t);
+ }
+}
+
+#ifndef ptable_walk
+STATIC void ptable_walk(pTHX_ ptable * const t, void (*cb)(pTHX_ ptable_ent *ent, void *userdata), void *userdata) {
+#define ptable_walk(T, CB, UD) ptable_walk(aTHX_ (T), (CB), (UD))
+ if (t && t->items) {
+  register ptable_ent ** const array = t->ary;
+  UV i = t->max;
+  do {
+   ptable_ent *entry;
+   for (entry = array[i]; entry; entry = entry->next)
+    cb(aTHX_ entry, userdata);
+  } while (i--);
+ }
+}
+#endif /* !ptable_walk */
+
+STATIC void PTABLE_PREFIX(_clear)(pPTBL_ ptable * const t) {
+ if (t && t->items) {
+  register ptable_ent ** const array = t->ary;
+  UV i = t->max;
+
+  do {
+   ptable_ent *entry = array[i];
+   while (entry) {
+    ptable_ent * const oentry = entry;
+    void *val = oentry->val;
+    entry = entry->next;
+    PTABLE_VAL_FREE(val);
+    PerlMemShared_free(oentry);
+   }
+   array[i] = NULL;
+  } while (i--);
+
+  t->items = 0;
+ }
+}
+
+STATIC void PTABLE_PREFIX(_free)(pPTBL_ ptable * const t) {
+ if (!t)
+  return;
+ PTABLE_PREFIX(_clear)(aPTBL_ t);
+ PerlMemShared_free(t->ary);
+ PerlMemShared_free(t);
+}
+
+#undef pPTBL
+#undef pPTBL_
+#undef aPTBL
+#undef aPTBL_
+
+#undef PTABLE_NAME
+#undef PTABLE_VAL_FREE
diff --git a/ext/arybase/t/aeach.t b/ext/arybase/t/aeach.t
new file mode 100644 (file)
index 0000000..f56d39e
--- /dev/null
@@ -0,0 +1,45 @@
+use warnings;
+use strict;
+
+BEGIN {
+       if("$]" < 5.011) {
+               require Test::More;
+               Test::More::plan(skip_all => "no array each on this Perl");
+       }
+}
+
+use Test::More tests => 2;
+
+our @activity;
+
+$[ = 3;
+
+our @t0 = qw(a b c);
+@activity = ();
+foreach(0..5) {
+       push @activity, [ each(@t0) ];
+}
+is_deeply \@activity, [
+       [ 3, "a" ],
+       [ 4, "b" ],
+       [ 5, "c" ],
+       [],
+       [ 3, "a" ],
+       [ 4, "b" ],
+];
+
+our @t1 = qw(a b c);
+@activity = ();
+foreach(0..5) {
+       push @activity, [ scalar each(@t1) ];
+}
+is_deeply \@activity, [
+       [ 3 ],
+       [ 4 ],
+       [ 5 ],
+       [ undef ],
+       [ 3 ],
+       [ 4 ],
+];
+
+1;
diff --git a/ext/arybase/t/aelem.t b/ext/arybase/t/aelem.t
new file mode 100644 (file)
index 0000000..d6b8c38
--- /dev/null
@@ -0,0 +1,56 @@
+use warnings;
+use strict;
+
+use Test::More tests => 33;
+
+our @t = qw(a b c d e f);
+our $r = \@t;
+our($i3, $i4, $i8, $i9) = (3, 4, 8, 9);
+our @i4 = (3, 3, 3, 3);
+
+$[ = 3;
+
+is $t[3], "a";
+is $t[4], "b";
+is $t[8], "f";
+is $t[9], undef;
+is_deeply [ scalar $t[4] ], [ "b" ];
+is_deeply [ $t[4] ], [ "b" ];
+
+is $t[2], 'f';
+is $t[-1], 'f';
+is $t[1], 'e';
+is $t[-2], 'e';
+
+{
+ $[ = -3;
+ is $t[-3], 'a';
+}
+
+is $r->[3], "a";
+is $r->[4], "b";
+is $r->[8], "f";
+is $r->[9], undef;
+is_deeply [ scalar $r->[4] ], [ "b" ];
+is_deeply [ $r->[4] ], [ "b" ];
+
+is $t[$i3], "a";
+is $t[$i4], "b";
+is $t[$i8], "f";
+is $t[$i9], undef;
+is_deeply [ scalar $t[$i4] ], [ "b" ];
+is_deeply [ $t[$i4] ], [ "b" ];
+is_deeply [ scalar $t[@i4] ], [ "b" ];
+is_deeply [ $t[@i4] ], [ "b" ];
+
+is $r->[$i3], "a";
+is $r->[$i4], "b";
+is $r->[$i8], "f";
+is $r->[$i9], undef;
+is_deeply [ scalar $r->[$i4] ], [ "b" ];
+is_deeply [ $r->[$i4] ], [ "b" ];
+is_deeply [ scalar $r->[@i4] ], [ "b" ];
+is_deeply [ $r->[@i4] ], [ "b" ];
+
+
+1;
diff --git a/ext/arybase/t/akeys.t b/ext/arybase/t/akeys.t
new file mode 100644 (file)
index 0000000..45af13b
--- /dev/null
@@ -0,0 +1,40 @@
+use warnings;
+use strict;
+
+BEGIN {
+       if("$]" < 5.011) {
+               require Test::More;
+               Test::More::plan(skip_all => "no array keys on this Perl");
+       }
+}
+
+use Test::More tests => 8;
+
+our @t;
+
+$[ = 3;
+
+@t = ();
+is_deeply [ scalar keys @t ], [ 0 ];
+is_deeply [ keys @t ], [];
+
+@t = qw(a b c d e f);
+is_deeply [ scalar keys @t ], [ 6 ];
+is_deeply [ keys @t ], [ 3, 4, 5, 6, 7, 8 ];
+
+SKIP: {
+       skip "no lexical \$_", 4 unless eval q{my $_; 1};
+       eval q{
+               my $_;
+
+               @t = ();
+               is_deeply [ scalar keys @t ], [ 0 ];
+               is_deeply [ keys @t ], [];
+
+               @t = qw(a b c d e f);
+               is_deeply [ scalar keys @t ], [ 6 ];
+               is_deeply [ keys @t ], [ 3, 4, 5, 6, 7, 8 ];
+       };
+}
+
+1;
diff --git a/ext/arybase/t/arybase.t b/ext/arybase/t/arybase.t
new file mode 100644 (file)
index 0000000..230ee7e
--- /dev/null
@@ -0,0 +1,33 @@
+#!perl
+
+# Basic tests for $[ as a variable
+
+use Test::More tests => 7;
+
+sub outside_base_scope { return "${'['}" }
+
+$[ = 3;
+my $base = \$[;
+is "$$base", 3, 'retval of $[';
+is outside_base_scope, 0, 'retval of $[ outside its scope';
+
+${'['} = 3;
+pass('run-time $[ = 3 assignment (in $[ = 3 scope)');
+{
+  $[ = 0;
+  ${'['} = 0;
+  pass('run-time $[ = 0 assignment (in $[ = 3 scope)');
+}
+
+eval { ${'['} = 1 }; my $f = __FILE__; my $l = __LINE__;
+is $@, "That use of \$[ is unsupported at $f line $l.\n",
+   "error when setting $[ to integer other than current base at run-time";
+
+$[ = 6.7;
+is "$[", 6, '$[ is an integer';
+
+eval { my $x = 45; $[ = \$x }; $l = __LINE__;
+is $@, "That use of \$[ is unsupported at $f line $l.\n",
+   'error when setting $[ to ref';
+
+1;
diff --git a/ext/arybase/t/aslice.t b/ext/arybase/t/aslice.t
new file mode 100644 (file)
index 0000000..38aa87b
--- /dev/null
@@ -0,0 +1,42 @@
+use warnings;
+use strict;
+
+use Test::More tests => 18;
+
+our @t = qw(a b c d e f);
+our $r = \@t;
+our @i4 = (3, 5, 3, 5);
+
+$[ = 3;
+
+is_deeply [ scalar @t[3,4] ], [ qw(b) ];
+is_deeply [ @t[3,4,8,9] ], [ qw(a b f), undef ];
+is_deeply [ scalar @t[@i4] ], [ qw(c) ];
+is_deeply [ @t[@i4] ], [ qw(a c a c) ];
+is_deeply [ scalar @{$r}[3,4] ], [ qw(b) ];
+is_deeply [ @{$r}[3,4,8,9] ], [ qw(a b f), undef ];
+is_deeply [ scalar @{$r}[@i4] ], [ qw(c) ];
+is_deeply [ @{$r}[@i4] ], [ qw(a c a c) ];
+
+is_deeply [ @t[2,-1,1,-2] ], [ qw(f f e e) ];
+{
+ $[ = -3;
+ is_deeply [@t[-3,()]], ['a'];
+}
+
+SKIP: {
+       skip "no lexical \$_", 8 unless eval q{my $_; 1};
+       eval q{
+               my $_;
+               is_deeply [ scalar @t[3,4] ], [ qw(b) ];
+               is_deeply [ @t[3,4,8,9] ], [ qw(a b f), undef ];
+               is_deeply [ scalar @t[@i4] ], [ qw(c) ];
+               is_deeply [ @t[@i4] ], [ qw(a c a c) ];
+               is_deeply [ scalar @{$r}[3,4] ], [ qw(b) ];
+               is_deeply [ @{$r}[3,4,8,9] ], [ qw(a b f), undef ];
+               is_deeply [ scalar @{$r}[@i4] ], [ qw(c) ];
+               is_deeply [ @{$r}[@i4] ], [ qw(a c a c) ];
+       };
+}
+
+1;
diff --git a/ext/arybase/t/av2arylen.t b/ext/arybase/t/av2arylen.t
new file mode 100644 (file)
index 0000000..988cca9
--- /dev/null
@@ -0,0 +1,26 @@
+use warnings;
+use strict;
+
+use Test::More tests => 8;
+
+our @t = qw(a b c d e f);
+our $r = \@t;
+
+$[ = 3;
+
+is_deeply [ scalar $#t ], [ 8 ];
+is_deeply [ $#t ], [ 8 ];
+is_deeply [ scalar $#$r ], [ 8 ];
+is_deeply [ $#$r ], [ 8 ];
+
+my $arylen=\$#t;
+push @t, 'g';
+is 0+$$arylen, 9;
+$[ = 4;
+is 0+$$arylen, 10;
+--$$arylen;
+$[ = 3;
+is 0+$$arylen, 8;
+is 0+$#t, 8;
+
+1;
diff --git a/ext/arybase/t/index.t b/ext/arybase/t/index.t
new file mode 100644 (file)
index 0000000..58efe74
--- /dev/null
@@ -0,0 +1,23 @@
+use warnings;
+use strict;
+
+use Test::More tests => 12;
+
+our $t = "abcdefghijkl";
+
+$[ = 3;
+
+is index($t, "cdef"), 5;
+is index($t, "cdef", 3), 5;
+is index($t, "cdef", 4), 5;
+is index($t, "cdef", 5), 5;
+is index($t, "cdef", 6), 2;
+is index($t, "cdef", 7), 2;
+is rindex($t, "cdef"), 5;
+is rindex($t, "cdef", 7), 5;
+is rindex($t, "cdef", 6), 5;
+is rindex($t, "cdef", 5), 5;
+is rindex($t, "cdef", 4), 2;
+is rindex($t, "cdef", 3), 2;
+
+1;
diff --git a/ext/arybase/t/lslice.t b/ext/arybase/t/lslice.t
new file mode 100644 (file)
index 0000000..6247a5e
--- /dev/null
@@ -0,0 +1,33 @@
+use warnings;
+use strict;
+
+use Test::More tests => 11;
+
+our @i4 = (3, 5, 3, 5);
+
+$[ = 3;
+
+is_deeply [ scalar qw(a b c d e f)[3,4] ], [ qw(b) ];
+is_deeply [ qw(a b c d e f)[3,4,8,9] ], [ qw(a b f), undef ];
+is_deeply [ scalar qw(a b c d e f)[@i4] ], [ qw(c) ];
+is_deeply [ qw(a b c d e f)[@i4] ], [ qw(a c a c) ];
+
+is_deeply [ qw(a b c d e f)[-1,-2] ], [ qw(f e) ];
+is_deeply [ qw(a b c d e f)[2,1] ], [ qw(f e) ];
+{
+ $[ = -3;
+ is_deeply [qw(a b c d e f)[-3]], ['a'];
+}
+
+SKIP: {
+       skip "no lexical \$_", 4 unless eval q{my $_; 1};
+       eval q{
+               my $_;
+               is_deeply [ scalar qw(a b c d e f)[3,4] ], [ qw(b) ];
+               is_deeply [ qw(a b c d e f)[3,4,8,9] ], [ qw(a b f), undef ];
+               is_deeply [ scalar qw(a b c d e f)[@i4] ], [ qw(c) ];
+               is_deeply [ qw(a b c d e f)[@i4] ], [ qw(a c a c) ];
+       };
+}
+
+1;
diff --git a/ext/arybase/t/pos.t b/ext/arybase/t/pos.t
new file mode 100644 (file)
index 0000000..f2f6504
--- /dev/null
@@ -0,0 +1,35 @@
+use warnings;
+use strict;
+
+use Test::More tests => 12;
+
+our $t = "abcdefghi";
+scalar($t =~ /abcde/g);
+our $r = \$t;
+
+$[ = 3;
+
+is_deeply [ scalar pos($t) ], [ 8 ];
+is_deeply [ pos($t) ], [ 8 ];
+is_deeply [ scalar pos($$r) ], [ 8 ];
+is_deeply [ pos($$r) ], [ 8 ];
+
+scalar($t =~ /x/g);
+
+is_deeply [ scalar pos($t) ], [ undef ];
+is_deeply [ pos($t) ], [ undef ];
+is_deeply [ scalar pos($$r) ], [ undef ];
+is_deeply [ pos($$r) ], [ undef ];
+
+is pos($t), undef;
+pos($t) = 5;
+is 0+pos($t), 5;
+is pos($t), 2;
+my $posr =\ pos($t);
+$$posr = 4;
+{
+  $[ = 0;
+  is 0+$$posr, 1;
+}
+
+1;
diff --git a/ext/arybase/t/scope.t b/ext/arybase/t/scope.t
new file mode 100644 (file)
index 0000000..5fb0993
--- /dev/null
@@ -0,0 +1,43 @@
+use warnings;
+use strict;
+
+use Test::More tests => 14;
+
+our @t = qw(a b c d e f);
+
+is $t[3], "d";
+$[ = 3;
+is $t[3], "a";
+{
+       is $t[3], "a";
+       $[ = -1;
+       is $t[3], "e";
+       $[ = +0;
+       is $t[3], "d";
+       $[ = +1;
+       is $t[3], "c";
+       $[ = 0;
+       is $t[3], "d";
+}
+is $t[3], "a";
+{
+       local $[ = -1;
+       is $t[3], "e";
+}
+is $t[3], "a";
+{
+       ($[) = -1;
+       is $t[3], "e";
+}
+is $t[3], "a";
+use t::scope_0;
+is scope0_test(), "d";
+
+
+is eval(q{
+       $[ = 3;
+       BEGIN { my $x = "foo\x{666}"; $x =~ /foo\p{Alnum}/; }
+       $t[3];
+}), "a";
+
+1;
diff --git a/ext/arybase/t/scope_0.pm b/ext/arybase/t/scope_0.pm
new file mode 100644 (file)
index 0000000..9f6c783
--- /dev/null
@@ -0,0 +1,6 @@
+use warnings;
+use strict;
+
+sub main::scope0_test { $main::t[3] }
+
+1;
diff --git a/ext/arybase/t/splice.t b/ext/arybase/t/splice.t
new file mode 100644 (file)
index 0000000..e2db280
--- /dev/null
@@ -0,0 +1,65 @@
+use warnings;
+use strict;
+
+use Test::More tests => 23;
+
+our @t;
+our @i5 = (3, 3, 3, 3, 3);
+
+$[ = 3;
+
+@t = qw(a b c d e f);
+is_deeply [ scalar splice @t ], [qw(f)];
+is_deeply \@t, [];
+
+@t = qw(a b c d e f);
+is_deeply [ splice @t ], [qw(a b c d e f)];
+is_deeply \@t, [];
+
+@t = qw(a b c d e f);
+is_deeply [ scalar splice @t, 5 ], [qw(f)];
+is_deeply \@t, [qw(a b)];
+
+@t = qw(a b c d e f);
+is_deeply [ splice @t, 5 ], [qw(c d e f)];
+is_deeply \@t, [qw(a b)];
+
+@t = qw(a b c d e f);
+is_deeply [ scalar splice @t, @i5 ], [qw(f)];
+is_deeply \@t, [qw(a b)];
+
+@t = qw(a b c d e f);
+is_deeply [ splice @t, @i5 ], [qw(c d e f)];
+is_deeply \@t, [qw(a b)];
+
+@t = qw(a b c d e f);
+is_deeply [ scalar splice @t, 5, 2 ], [qw(d)];
+is_deeply \@t, [qw(a b e f)];
+
+@t = qw(a b c d e f);
+is_deeply [ splice @t, 5, 2 ], [qw(c d)];
+is_deeply \@t, [qw(a b e f)];
+
+@t = qw(a b c d e f);
+is_deeply [ scalar splice @t, 5, 2, qw(x y z) ], [qw(d)];
+is_deeply \@t, [qw(a b x y z e f)];
+
+@t = qw(a b c d e f);
+is_deeply [ splice @t, 5, 2, qw(x y z) ], [qw(c d)];
+is_deeply \@t, [qw(a b x y z e f)];
+
+@t = qw(a b c d e f);
+splice @t, -4, 1;
+is_deeply \@t, [qw(a b d e f)];
+
+@t = qw(a b c d e f);
+splice @t, 1, 1;
+is_deeply \@t, [qw(a b c d f)];
+
+$[ = -3;
+
+@t = qw(a b c d e f);
+splice @t, -3, 1;
+is_deeply \@t, [qw(b c d e f)];
+
+1;
diff --git a/ext/arybase/t/substr.t b/ext/arybase/t/substr.t
new file mode 100644 (file)
index 0000000..793293b
--- /dev/null
@@ -0,0 +1,22 @@
+use warnings;
+use strict;
+
+use Test::More tests => 6;
+
+our $t;
+
+$[ = 3;
+
+$t = "abcdef";
+is substr($t, 5), "cdef";
+is $t, "abcdef";
+
+$t = "abcdef";
+is substr($t, 5, 2), "cd";
+is $t, "abcdef";
+
+$t = "abcdef";
+is substr($t, 5, 2, "xyz"), "cd";
+is $t, "abxyzef";
+
+1;
diff --git a/gv.c b/gv.c
index 1319970..0010da7 100644 (file)
--- a/gv.c
+++ b/gv.c
@@ -1278,6 +1278,7 @@ S_require_tie_mod(pTHX_ GV *gv, const char *varpv, SV* namesv, const char *methp
        char varname = *varpv; /* varpv might be clobbered by load_module,
                                  so save it. For the moment it's always
                                  a single char. */
+       const char type = varname == '[' ? '$' : '%';
        dSP;
        ENTER;
        if ( flags & 1 )
@@ -1289,11 +1290,11 @@ S_require_tie_mod(pTHX_ GV *gv, const char *varpv, SV* namesv, const char *methp
        SPAGAIN;
        stash = gv_stashsv(namesv, 0);
        if (!stash)
-           Perl_croak(aTHX_ "panic: Can't use %%%c because %"SVf" is not available",
-                   varname, SVfARG(namesv));
+           Perl_croak(aTHX_ "panic: Can't use %c%c because %"SVf" is not available",
+                   type, varname, SVfARG(namesv));
        else if (!gv_fetchmethod(stash, methpv))
-           Perl_croak(aTHX_ "panic: Can't use %%%c because %"SVf" does not support method %s",
-                   varname, SVfARG(namesv), methpv);
+           Perl_croak(aTHX_ "panic: Can't use %c%c because %"SVf" does not support method %s",
+                   type, varname, SVfARG(namesv), methpv);
     }
     SvREFCNT_dec(namesv);
     return stash;
@@ -1659,12 +1660,15 @@ Perl_gv_fetchpvn_flags(pTHX_ const char *nambeg, STRLEN full_len, I32 flags,
        if (add) {
            GvMULTI_on(gv);
            gv_init_svtype(gv, sv_type);
-           if (len == 1 && stash == PL_defstash
-               && (sv_type == SVt_PVHV || sv_type == SVt_PVGV)) {
+           if (len == 1 && stash == PL_defstash) {
+             if (sv_type == SVt_PVHV || sv_type == SVt_PVGV) {
                if (*name == '!')
                    require_tie_mod(gv, "!", newSVpvs("Errno"), "TIEHASH", 1);
                else if (*name == '-' || *name == '+')
                    require_tie_mod(gv, name, newSVpvs("Tie::Hash::NamedCapture"), "TIEHASH", 0);
+             }
+             if ((sv_type==SVt_PV || sv_type==SVt_PVGV) && *name == '[')
+               require_tie_mod(gv,name,newSVpvs("arybase"),"FETCH",0);
            }
            else if (len == 3 && sv_type == SVt_PVAV
                  && strnEQ(name, "ISA", 3)
@@ -1940,6 +1944,13 @@ Perl_gv_fetchpvn_flags(pTHX_ const char *nambeg, STRLEN full_len, I32 flags,
                hv_magic(hv, NULL, PERL_MAGIC_hints);
            }
            goto magicalize;
+       case '[':               /* $[ */
+           if (sv_type == SVt_PV || sv_type == SVt_PVGV) {
+               if (addmg) (void)hv_store(stash,name,len,(SV *)gv,0);
+               require_tie_mod(gv,name,newSVpvs("arybase"),"FETCH",0);
+               addmg = 0;
+           }
+            break;
        case '\023':    /* $^S */
        ro_magicalize:
            SvREADONLY_on(GvSVn(gv));
@@ -1954,7 +1965,6 @@ Perl_gv_fetchpvn_flags(pTHX_ const char *nambeg, STRLEN full_len, I32 flags,
        case '7':               /* $7 */
        case '8':               /* $8 */
        case '9':               /* $9 */
-       case '[':               /* $[ */
        case '^':               /* $^ */
        case '~':               /* $~ */
        case '=':               /* $= */
diff --git a/mg.c b/mg.c
index 1b24ce8..8c986a5 100644 (file)
--- a/mg.c
+++ b/mg.c
@@ -2723,10 +2723,6 @@ Perl_magic_set(pTHX_ SV *sv, MAGIC *mg)
            PL_ors_sv = NULL;
        }
        break;
-    case '[':
-       if (SvIV(sv) != 0)
-           Perl_croak(aTHX_ "Assigning non-zero to $[ is no longer possible");
-       break;
     case '?':
 #ifdef COMPLEX_STATUS
        if (PL_localizing == 2) {
index ab5b8db..6f2416a 100644 (file)
@@ -238,11 +238,6 @@ spots.  This is now heavily deprecated.
 
 (P) A general assertion failed.  The file in question must be examined.
 
-=item Assigning non-zero to $[ is no longer possible
-
-(F) The special variable C<$[>, deprecated in older perls, is now a fixed
-zero value, because the feature that it used to control has been removed.
-
 =item Assignment to both a list and a scalar
 
 (F) If you assign to a conditional operator, the 2nd and 3rd arguments
@@ -4519,6 +4514,21 @@ a dirhandle.  Check your control flow.
 (W unopened) You tried to use the tell() function on a filehandle that
 was either never opened or has since been closed.
 
+=item That use of $[ is unsupported
+
+(F) Assignment to C<$[> is now strictly circumscribed, and interpreted
+as a compiler directive.  You may say only one of
+
+    $[ = 0;
+    $[ = 1;
+    ...
+    local $[ = 0;
+    local $[ = 1;
+    ...
+
+This is to prevent the problem of one module changing the array base out
+from under another module inadvertently.  See L<perlvar/$[> and L<arybase>.
+
 =item The crypt() function is unimplemented due to excessive paranoia
 
 (F) Configure couldn't find the crypt() function on your machine,
index 9bd1820..68d2acf 100644 (file)
@@ -2085,16 +2085,27 @@ Removed in Perl 5.10.
 =item $[
 X<$[> X<$ARRAY_BASE>
 
-C<$[> was a variable that you could use to offset the indexing of arrays
-and strings.  After a deprecation cycle, the feature was removed in
-Perl 5.16.  Two old ways of coping with the variability of the index
-offset, which were rendered obsolete in Perl 5.000 when C<$[> became
-effectively lexically scoped, are still supported: you can read it
-(always yielding zero) and you can assign zero to it.
+This variable stores the index of the first element in an array, and
+of the first character in a substring.  The default is 0, but you could
+theoretically set it to 1 to make Perl behave more like B<awk> (or Fortran)
+when subscripting and when evaluating the index() and substr() functions.
 
-Deprecated in Perl 5.12.
+As of release 5 of Perl, assignment to C<$[> is treated as a compiler
+directive, and cannot influence the behavior of any other file.
+(That's why you can only assign compile-time constants to it.)
+Its use is highly discouraged.
+
+Prior to Perl 5.10, assignment to C<$[> could be seen from outer lexical
+scopes in the same file, unlike other compile-time directives (such as
+L<strict>).  Using local() on it would bind its value strictly to a lexical
+block.  Now it is always lexically scoped.
+
+As of Perl 5.16, it is implemented by the L<arybase> module.  See
+L<arybase> for more details on its behaviour.
 
-Removed in Perl 5.16.
+Mnemonic: [ begins subscripts.
+
+Deprecated in Perl 5.12.
 
 =item $OLD_PERL_VERSION
 
index 369cf31..fe5045a 100644 (file)
@@ -1,13 +1,16 @@
 #!perl -w
 use strict;
 
-require './test.pl';
+BEGIN {
+ require './test.pl';
+ skip_all_if_miniperl();
+}
 
 plan (tests => 4);
 
 is(eval('$['), 0);
 is(eval('$[ = 0; 123'), 123);
-is(eval('$[ = 1; 123'), undef);
-like($@, qr/\AAssigning non-zero to \$\[ is no longer possible/);
+is(eval('$[ = 1; 123'), 123);
+ok $INC{'arybase.pm'};
 
 1;
index 8c2c508..d123670 100644 (file)
@@ -15,7 +15,7 @@ BEGIN {
 # does not mention any special variables, but that could easily change.
 BEGIN {
     # not available in miniperl
-    my %non_mini = map { $_ => 1 } qw(+ -);
+    my %non_mini = map { $_ => 1 } qw(+ - [);
     for (qw(
        SIG ^OPEN ^TAINT ^UNICODE ^UTF8LOCALE ^WARNING_BITS 1 2 3 4 5 6 7 8
        9 42 & ` ' : ? ! _ - [ ^ ~ = % . ( ) < > \ / $ | + ; ] ^A ^C ^D
index 20454dd..850722f 100644 (file)
@@ -111,6 +111,7 @@ SOM
 splain
 sprintf(3)
 stat(2)
+String::Base
 String::Scanf
 Switch
 tar(1)