+#ifndef XSH_PEEP_H
+#define XSH_PEEP_H 1
+
+#include "caps.h" /* XSH_HAS_PERL(), XSH_THREADSAFE */
+#include "util.h" /* XSH_ASSERT(), NOOP */
+
+#ifdef XSH_THREADS_H
+# error threads.h must be loaded at the very end
+#endif
+
+#ifndef XSH_HAS_RPEEP
+# define XSH_HAS_RPEEP XSH_HAS_PERL(5, 13, 5)
+#endif
+
+#define PTABLE_USE_DEFAULT 1
+#define PTABLE_NEED_DELETE 0
+
+#include "ptable.h"
+
+#define ptable_seen_store(T, K, V) ptable_default_store(aPTBL_ (T), (K), (V))
+#define ptable_seen_clear(T) ptable_default_clear(aPTBL_ (T))
+#define ptable_seen_free(T) ptable_default_free(aPTBL_ (T))
+
+#define XSH_THREADS_PEEP_CONTEXT 1
+
+typedef struct {
+#if XSH_HAS_RPEEP
+ peep_t old_rpeep;
+#else
+ peep_t old_peep;
+#endif
+ ptable *seen;
+} xsh_peep_cxt_t;
+
+static xsh_peep_cxt_t *xsh_peep_get_cxt(pTHX);
+
+static void xsh_peep_rec(pTHX_ OP *o, ptable *seen);
+
+#if XSH_HAS_RPEEP
+
+static void xsh_rpeep(pTHX_ OP *o) {
+ ptable *seen;
+ xsh_peep_cxt_t *cxt = xsh_peep_get_cxt(aTHX);
+
+ cxt->old_rpeep(aTHX_ o);
+
+ seen = cxt->seen;
+ XSH_ASSERT(seen);
+
+ ptable_seen_clear(seen);
+
+ xsh_peep_rec(aTHX_ o, seen);
+
+ ptable_seen_clear(seen);
+
+ return;
+}
+
+#define xsh_peep_maybe_recurse(O, S) NOOP
+
+#else /* XSH_HAS_RPEEP */
+
+static void xsh_peep(pTHX_ OP *o) {
+ ptable *seen;
+ xsh_peep_cxt_t *cxt = xsh_peep_get_cxt(aTHX);
+
+ cxt->old_peep(aTHX_ o); /* Will call the rpeep */
+
+ seen = cxt->seen;
+ XSH_ASSERT(seen);
+
+ ptable_seen_clear(seen);
+
+ xsh_peep_rec(aTHX_ o, seen);
+
+ ptable_seen_clear(seen);
+
+ return;
+}
+
+static void xsh_peep_maybe_recurse(pTHX_ OP *o, ptable *seen) {
+#define xsh_peep_maybe_recurse(O, S) xsh_peep_maybe_recurse(aTHX_ (O), (S))
+ switch (o->op_type) {
+ case OP_MAPWHILE:
+ case OP_GREPWHILE:
+ case OP_AND:
+ case OP_OR:
+ case OP_ANDASSIGN:
+ case OP_ORASSIGN:
+ case OP_COND_EXPR:
+ case OP_RANGE:
+#if XSH_HAS_PERL(5, 10, 0)
+ case OP_ONCE:
+ case OP_DOR:
+ case OP_DORASSIGN:
+#endif
+ xsh_peep_rec(aTHX_ cLOGOPo->op_other, seen);
+ break;
+ case OP_ENTERLOOP:
+ case OP_ENTERITER:
+ xsh_peep_rec(aTHX_ cLOOPo->op_redoop, seen);
+ xsh_peep_rec(aTHX_ cLOOPo->op_nextop, seen);
+ xsh_peep_rec(aTHX_ cLOOPo->op_lastop, seen);
+ break;
+#if XSH_HAS_PERL(5, 9, 5)
+ case OP_SUBST:
+ xsh_peep_rec(aTHX_ cPMOPo->op_pmstashstartu.op_pmreplstart, seen);
+ break;
+#else
+ case OP_QR:
+ case OP_MATCH:
+ case OP_SUBST:
+ xsh_peep_rec(aTHX_ cPMOPo->op_pmreplstart, seen);
+ break;
+#endif
+ }
+
+ return;
+}
+
+#endif /* !XSH_HAS_RPEEP */
+
+static int xsh_peep_seen(pTHX_ OP *o, ptable *seen) {
+#define xsh_peep_seen(O, S) xsh_peep_seen(aTHX_ (O), (S))
+#if XSH_HAS_RPEEP
+ switch (o->op_type) {
+ case OP_NEXTSTATE:
+ case OP_DBSTATE:
+ case OP_UNSTACK:
+ case OP_STUB:
+ break;
+ default:
+ return 0;
+ }
+#endif /* XSH_HAS_RPEEP */
+
+ if (ptable_fetch(seen, o))
+ return 1;
+
+ ptable_seen_store(seen, o, o);
+
+ return 0;
+}
+
+static void xsh_peep_local_setup(pTHX_ xsh_peep_cxt_t *cxt) {
+#if XSH_HAS_RPEEP
+ if (PL_rpeepp != xsh_rpeep) {
+ cxt->old_rpeep = PL_rpeepp;
+ PL_rpeepp = xsh_rpeep;
+ } else {
+ cxt->old_rpeep = 0;
+ }
+#else
+ if (PL_peepp != xsh_peep) {
+ cxt->old_peep = PL_peepp;
+ PL_peepp = xsh_peep;
+ } else {
+ cxt->old_peep = 0;
+ }
+#endif
+
+ cxt->seen = ptable_new(32);
+}
+
+static void xsh_peep_local_teardown(pTHX_ xsh_peep_cxt_t *cxt) {
+ ptable_seen_free(cxt->seen);
+ cxt->seen = NULL;
+
+#if XSH_HAS_RPEEP
+ if (cxt->old_rpeep) {
+ PL_rpeepp = cxt->old_rpeep;
+ cxt->old_rpeep = 0;
+ }
+#else
+ if (cxt->old_peep) {
+ PL_peepp = cxt->old_peep;
+ cxt->old_peep = 0;
+ }
+#endif
+
+ return;
+}
+
+static void xsh_peep_clone(pTHX_ const xsh_peep_cxt_t *old_cxt, xsh_peep_cxt_t *new_cxt) {
+ new_cxt->seen = ptable_new(32);
+
+ return;
+}
+
+#endif /* XSH_PEEP_H */