The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#define PERL_NO_GET_CONTEXT
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>

#define NEED_newSV_type
#include "ppport.h"

#ifndef CxLABEL
#define CxLABEL(cx) ((cx)->blk_loop.label)
#endif

#ifndef CxFOREACH
#define CxFOREACH(cx) (CxTYPE(cx) == CXt_LOOP && CxITERVAR(cx) != NULL)
#endif

#ifndef CX_LOOP_NEXTOP_GET
#if PERL_BCDVERSION >= 0x5013005
#define CX_LOOP_NEXTOP_GET(cx) ((cx)->blk_loop.my_op->op_nextop)
#else
#define CX_LOOP_NEXTOP_GET(cx) ((cx)->blk_loop.next_op)
#endif
#endif

#ifdef CXt_LOOP_FOR /* >= 5.11 */
#define CxLOOP_FOR(cx)    (CxTYPE(cx) == CXt_LOOP_FOR)
#define CxLOOP_LAZYSV(cx) (CxTYPE(cx) == CXt_LOOP_LAZYSV)

#define CxITERARY(cx)     ((cx)->blk_loop.state_u.ary.ary)
#define CxITERARY_IS_STACK(cx) (CxITERARY(cx) == NULL)
#define CxITERIX(cx)      ((cx)->blk_loop.state_u.ary.ix)
#define CxLAZYSV_CUR(cx)  ((cx)->blk_loop.state_u.lazysv.cur)
#define CxLAZYSV_END(cx)  ((cx)->blk_loop.state_u.lazysv.end)
#define CxLAZYIV_CUR(cx)  ((cx)->blk_loop.state_u.lazyiv.cur)
#define CxLAZYIV_END(cx)  ((cx)->blk_loop.state_u.lazyiv.end)
#else
#define CxLOOP_FOR(cx)    (SvTYPE(CxITERARY(cx)) == SVt_PVAV)
#define CxLOOP_LAZYSV(cx) ((cx)->blk_loop.iterlval != NULL)

#define CxITERARY(cx)     ((cx)->blk_loop.iterary)
#define CxITERARY_IS_STACK(cx) (CxITERARY(cx) == PL_curstack)
#define CxITERIX(cx)      ((cx)->blk_loop.iterix)
#define CxLAZYSV_CUR(cx)  ((cx)->blk_loop.iterlval)
#define CxLAZYSV_END(cx)  ((SV*)(cx)->blk_loop.iterary)
#define CxLAZYIV_CUR(cx)  ((cx)->blk_loop.iterix)
#define CxLAZYIV_END(cx)  ((cx)->blk_loop.itermax)
#endif

#define LoopIsReversed(cx) (CX_LOOP_NEXTOP_GET(cx)->op_next->op_private & OPpITER_REVERSED ? TRUE : FALSE)


static PERL_CONTEXT*
my_find_cx(pTHX_ const OP* const loop_op){
	dVAR;
	PERL_CONTEXT* const cxstk = cxstack;
	I32 i;
	for (i = cxstack_ix; i >= 0; i--) {
		PERL_CONTEXT* const cx = &cxstk[i];
		if(CxFOREACH(cx) && CX_LOOP_NEXTOP_GET(cx) == loop_op){
			return cx;
		}
	}

	Perl_croak(aTHX_ "Out of scope for the foreach iterator");
	return NULL;
}

static PERL_CONTEXT*
my_find_foreach(pTHX_ SV* const label){
	dVAR;
	PERL_CONTEXT* const cxstk  = cxstack;
	const char* const label_pv = SvOK(label) ? SvPV_nolen_const(label) : NULL;
	I32 i;

	for (i = cxstack_ix; i >= 0; i--) {
		PERL_CONTEXT* const cx = &cxstk[i];
		if(CxFOREACH(cx)){
			if(label_pv){
				if(CxLABEL(cx) && strEQ(CxLABEL(cx), label_pv)){
					return cx;
				}
			}
			else{
				return cx;
			}
		}
	}

	if(label_pv){
		Perl_croak(aTHX_ "No foreach loops found for \"%s\"", label_pv);
	}
	else{
		Perl_croak(aTHX_ "No foreach loops found");
	}
	return NULL; /* not reached */
}

typedef SV* SVREF;

MODULE = B::Foreach::Iterator	PACKAGE = B::Foreach::Iterator

PROTOTYPES: DISABLE

SV*
iter(label = NULL)
PREINIT:
	const PERL_CONTEXT* const cx = my_find_foreach(aTHX_ items == 1 ? ST(0) : &PL_sv_undef);
	SV* const iterator           = newSV_type(SVt_PVMG);
CODE:
	sv_setiv(iterator, PTR2IV(CX_LOOP_NEXTOP_GET(cx)));
	RETVAL = sv_bless(newRV_noinc(iterator), GvSTASH(CvGV(cv)));
OUTPUT:
	RETVAL

#define need_increment (ix == 0)

#define ITERMAX(cx) (av_is_stack ? cx->blk_oldsp : AvFILL(av))

#ifdef CXt_LOOP_FOR
#define ITERMIN(cx) (av_is_stack ? cx->blk_loop.resetsp + 1 : 0)
#else
#define ITERMIN(cx) (cx->blk_loop.itermax)
#endif

SV*
next(SVREF iterator)
ALIAS:
	next    = 0
	peek    = 1
	is_last = 2
PREINIT:
	PERL_CONTEXT* cx;
CODE:
	cx      = my_find_cx(aTHX_ INT2PTR(OP*, SvIV(iterator)));
	RETVAL  = NULL;

	/* see also pp_iter() in pp_hot.c */
	if(CxLOOP_FOR(cx)) { /* foreach(list) */
		bool const av_is_stack = CxITERARY_IS_STACK(cx);
		AV*  const av          = av_is_stack ? PL_curstack : CxITERARY(cx);
		bool const reversed    = LoopIsReversed(cx);
		bool const in_range    = reversed ? (--CxITERIX(cx) >= ITERMIN(cx))
		                                  : (++CxITERIX(cx) <= ITERMAX(cx));

		if (in_range){
			if (SvMAGICAL(av) || AvREIFY(av)){
				SV** const svp = av_fetch(av, CxITERIX(cx), FALSE);
				if(svp) RETVAL = *svp;
			}
			else{
				RETVAL = AvARRAY(av)[CxITERIX(cx)];
			}
		}

		if (!need_increment){
			reversed ? ++CxITERIX(cx) : --CxITERIX(cx);
		}
	}
	else { /* foreach (min .. max) */
		if(CxLOOP_LAZYSV(cx)) {
			SV* const cur = CxLAZYSV_CUR(cx);

			if (strNE(SvPV_nolen_const(cur), "0")){
				if(need_increment){
					dXSTARG;

					RETVAL = TARG;
					sv_setsv(RETVAL, cur);

					if(sv_eq(cur, CxLAZYSV_END(cx))){
						sv_setiv(cur, 0);
					}
					else{
						sv_inc(cur);
					}
				}
				else{
					RETVAL = cur;
				}
			}
		}
		else { /* LOOP_LAZYIV */
			if (CxLAZYIV_CUR(cx) <= CxLAZYIV_END(cx)){
				dXSTARG;
				RETVAL = TARG;
				sv_setiv(RETVAL, CxLAZYIV_CUR(cx));
			}

			if (need_increment) CxLAZYIV_CUR(cx)++;
		}
	}

	if(ix != 2){ /* next(), peek() */
		if(!RETVAL){
			RETVAL = &PL_sv_undef;
		}
	}
	else{ /* is_last() */
		RETVAL = boolSV(RETVAL == NULL);
	}

	ST(0) = RETVAL;
	XSRETURN(1);


const char*
label(SVREF iterator)
PREINIT:
	const PERL_CONTEXT* cx;
CODE:
	cx     = my_find_cx(aTHX_ INT2PTR(OP*, SvIV(iterator)));
	RETVAL = CxLABEL(cx); /* can be NULL */
OUTPUT:
	RETVAL