/*
	peephook.h - Helper header file to hook the peephole optimizer (PL_peepp)

	VERSION 0.02

	requires:
		MY_CXT_VARS
		my_peep_enabled(pTHX_ pMY_CXT_ OP*)
		my_peep(pTHX_ pMY_CXT_ COP*, OP*)
*/

#ifndef XS_PEEP_HOOK_H
#define XS_PEEP_HOOK_H

#include "ppport.h"
#include "ptr_table.h"

#define MY_CXT_KEY PACKAGE "::_guts" XS_VERSION
typedef struct{
	peep_t old_peepp;
	PTR_TBL_t* seen;

#ifdef MY_CXT_VARS
	MY_CXT_VARS
#endif
} my_cxt_t;
START_MY_CXT


#define PEEPHOOK_CONTEXT          \
		peep_t old_peepp; \
		PTR_TBL_t* seen;  \

static void my_peep(pTHX_ pMY_CXT_ COP* cop PERL_UNUSED_DECL, OP* o PERL_UNUSED_DECL);

static int  my_peep_enabled(pTHX_ pMY_CXT_ OP* o PERL_UNUSED_DECL);

static void
xs_peephook_dispatcher(pTHX_ pMY_CXT_ COP* cop, OP* o){
	dVAR;
	COP* const oldcop = cop;

	assert(MY_CXT.seen != NULL);

	for(; o; o = o->op_next){
		if(ptr_table_fetch(MY_CXT.seen, o)){
			break;
		}
		ptr_table_store(MY_CXT.seen, o, (void*)TRUE);

		my_peep(aTHX_ aMY_CXT_ cop, o);

		switch(o->op_type){
		case OP_NEXTSTATE:
		case OP_DBSTATE:
			cop = cCOPo; /* for context info */
			break;

		case OP_MAPWHILE:
		case OP_GREPWHILE:
		case OP_AND:
		case OP_OR:
#ifdef pp_dor
		case OP_DOR:
#endif
		case OP_ANDASSIGN:
		case OP_ORASSIGN:
#ifdef pp_dorassign
		case OP_DORASSIGN:
#endif
		case OP_COND_EXPR:
		case OP_RANGE:
#ifdef pp_once
		case OP_ONCE:
#endif
			xs_peephook_dispatcher(aTHX_ aMY_CXT_ cop, cLOGOPo->op_other);
			break;
		case OP_ENTERLOOP:
		case OP_ENTERITER:
			xs_peephook_dispatcher(aTHX_ aMY_CXT_ cop, cLOOPo->op_redoop);
			xs_peephook_dispatcher(aTHX_ aMY_CXT_ cop, cLOOPo->op_nextop);
			xs_peephook_dispatcher(aTHX_ aMY_CXT_ cop, cLOOPo->op_lastop);
			break;
		case OP_SUBST:
#if PERL_BCDVERSION >= 0x5010000
			xs_peephook_dispatcher(aTHX_ aMY_CXT_ cop, cPMOPo->op_pmstashstartu.op_pmreplstart);
#else
			xs_peephook_dispatcher(aTHX_ aMY_CXT_ cop, cPMOPo->op_pmreplstart);
#endif
			break;

		default:
			NOOP;
		}
	}

	cop = oldcop;
}

static void
xs_peephook_peep(pTHX_ OP* const o){
	dVAR;
	dMY_CXT;

	assert(o);

	if(my_peep_enabled(aTHX_ aMY_CXT_ o)){
		assert(MY_CXT.seen == NULL);
		MY_CXT.seen = ptr_table_new();

		xs_peephook_dispatcher(aTHX_ aMY_CXT_ PL_curcop, o);

		ptr_table_free(MY_CXT.seen);
		MY_CXT.seen = NULL;
	}

	MY_CXT.old_peepp(aTHX_ o);
}

#define PEEPHOOK_REGISTER() STMT_START {     \
		MY_CXT.old_peepp = PL_peepp; \
		PL_peepp = xs_peephook_peep; \
	} STMT_END

#endif /* XS_PEEP_HOOK_H */