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_PL_parser
#include "ppport.h"
#include "mgx.h"

static void
my_qerror(pTHX_ SV *err)
{
    dVAR;
    if (PL_in_eval)
        sv_catsv(ERRSV, err);
    else if (PL_errors)
        sv_catsv(PL_errors, err);
    else
        Perl_warn(aTHX_ "%"SVf, SVfARG(err));
    ++PL_error_count;
}
#undef qerror
#define qerror(msg) my_qerror(aTHX_ msg)


#define PACKAGE "Sub::Attribute"
#define META_ATTR "ATTR_SUB"

#define MY_CXT_KEY PACKAGE "::_guts" XS_VERSION
typedef struct {
    AV* queue;
    I32 debug;
} my_cxt_t;
START_MY_CXT

enum {
    SA_KLASS,
    SA_CODE,
    SA_NAME,
    SA_DATA,
    SA_METHOD
};

static void
apply_handler(pTHX_ pMY_CXT_ AV* const handler){
    SV* const klass        = AvARRAY(handler)[SA_KLASS];
    SV* const code_ref     = AvARRAY(handler)[SA_CODE];
    CV* const cv           = (CV*)SvRV(code_ref);
    SV* const name         = AvARRAY(handler)[SA_NAME];
    SV* const data         = AvARRAY(handler)[SA_DATA];
    SV* const method       = AvARRAY(handler)[SA_METHOD];
    dSP;

    if(sv_true(ERRSV)){ /* dying by bad attributes */
        qerror(ERRSV);
        return;
    }

    assert(CvGV(cv));
    assert(SvTYPE(method) == SVt_PVCV);

    if(MY_CXT.debug){
        warn("apply attribute :%s%s to &%s in %"SVf,
            GvNAME(CvGV((CV*)method)),
            SvOK(data) ? form("(%"SVf")", data) : "",
            GvNAME(CvGV(cv)),
            klass
        );
    }

    PUSHMARK(SP);
    EXTEND(SP, 5);

    PUSHs(klass);
    if(!CvANON(cv)){
        mPUSHs(newRV_inc((SV*)CvGV(cv)));
    }
    else{
        PUSHs(&PL_sv_undef); /* anonymous subroutines */
    }
    PUSHs(code_ref);
    PUSHs(name);
    PUSHs(data);

    PUTBACK;

    I32 retval;
    retval = call_sv(method, G_VOID | G_EVAL);
    PL_stack_sp -= retval;

    if(sv_true(ERRSV)){
        SV* const msg = sv_newmortal();
        sv_setpvf(msg, "Can't apply attribute %"SVf" because: %"SVf, name, ERRSV);
        qerror(msg);
    }
}

static int
sa_process_queue(pTHX_ SV* const sv, MAGIC* const mg){
    dMY_CXT;
    SV**       svp = AvARRAY(MY_CXT.queue);
    SV** const end = svp + AvFILLp(MY_CXT.queue) + 1;
    PERL_UNUSED_ARG(sv);
    PERL_UNUSED_ARG(mg);

    ENTER;
    SAVETMPS;

    while(svp != end){
        apply_handler(aTHX_ aMY_CXT_ (AV*)*svp);
        svp++;

        FREETMPS;
    }

    LEAVE;

    av_clear(MY_CXT.queue);
    return 0;
}

static SV*
sa_newSVsv_share(pTHX_ SV* const sv){
    STRLEN len;
    const char* const pv = SvPV_const(sv, len);
    return newSVpvn_share(pv, len, 0U);
}

static MGVTBL hook_scope_vtbl = {
    NULL, /* get */
    NULL, /* set */
    NULL, /* len */
    NULL, /* clear */
    sa_process_queue, /* free */
    NULL, /* copy */
    NULL, /* dup */
#ifdef MGf_LOCAL
    NULL,  /* local */
#endif
};


static MGVTBL attr_handler_vtbl;


MODULE = Sub::Attribute    PACKAGE = Sub::Attribute

PROTOTYPES: DISABLE

BOOT:
{
    const char* const d = PerlEnv_getenv("SUB_ATTRIBUTE_DEBUG");
    MY_CXT_INIT;
    MY_CXT.queue      = newAV();
    MY_CXT.debug      = (d && *d != '\0' && strNE(d, "0"));
}

void
CLONE(...)
CODE:
    MY_CXT_CLONE;
    MY_CXT.queue = newAV();
    PERL_UNUSED_VAR(items);

void
MODIFY_CODE_ATTRIBUTES(SV* klass, CV* code, ...)
PREINIT:
    dMY_CXT;
    HV* const hinthv = GvHVn(PL_hintgv);
    HV* stash;
    MAGIC* mg;
    I32 i;
PPCODE:
    mg = mg_find_by_vtbl((SV*)hinthv, &hook_scope_vtbl);
    if(!mg){
        sv_magicext((SV*)hinthv, NULL, PERL_MAGIC_ext, &hook_scope_vtbl, NULL, 0);
        PL_hints |= HINT_LOCALIZE_HH;
    }
    stash = gv_stashsv(klass, TRUE);
    klass = sa_newSVsv_share(aTHX_ klass);

    for(i = 2; i < items; i++){
        STRLEN attrlen;
        const char* const attr = SvPV_const(ST(i), attrlen);
        const char* data       = strchr(attr, '(');
        STRLEN  datalen        = attrlen - (data - attr) - 2;
        STRLEN const namelen   = data ? (STRLEN)(data - attr) : attrlen;
        GV* meth;

        if(data){
            data++; /* skip '(' */
            while(isSPACE(*data)){
                data++;
                datalen--;
            }
            while(isSPACE(data[datalen-1])){
                datalen--;
            }
        }

        if(strnEQ(attr, META_ATTR, sizeof(META_ATTR))){ /* meta attribute */
            if(!MgFind((SV*)code, &attr_handler_vtbl)){
                sv_magicext(
                    (SV*)code,
                    NULL, PERL_MAGIC_ext, &attr_handler_vtbl,
                    PACKAGE, 0
                );

                if(MY_CXT.debug){
                    warn("install attribute handler %"SVf"\n", PL_subname);
                }
            }
            continue;
        }

        meth = gv_fetchmeth_autoload(stash, attr, namelen, 0 /* special zero */);
        if(meth && MgFind((SV*)GvCV(meth), &attr_handler_vtbl)){
            AV* const handler = newAV();

            av_store(handler, SA_METHOD, SvREFCNT_inc_simple_NN((SV*)GvCV(meth)));
            av_store(handler, SA_KLASS,  SvREFCNT_inc_simple_NN(klass));
            av_store(handler, SA_CODE,   newRV_inc((SV*)code));
            av_store(handler, SA_NAME,   newSVpvn_share(attr, namelen, 0U));

            if(data){
                av_store(handler, SA_DATA,  newSVpvn(data, datalen));
            }

            av_push(MY_CXT.queue, (SV*)handler);
        }
        else{
            if(MY_CXT.debug){
                warn("ignore unrecognized attribute :%"SVf"\n", ST(i));
            }
#if PERL_BCDVERSION < 0x5008009
            /* See RT #53420 */
            {
                const char* const a = SvPV_nolen_const(ST(i));
                if(    strEQ(a, "lvalue")
                    || strEQ(a, "method")
                    || strEQ(a, "locked")
                    || strEQ(a, "unique")
                    || strEQ(a, "shared") ){
                    continue;
                }
            }
#endif
            XPUSHs(ST(i));
        }
    }