The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "perliol.h"

#include "ppport.h"

#define READING(fp, fail) do{\
		if( !(PerlIOBase(fp)->flags & PERLIO_F_CANREAD) ) return (fail);\
	} while(0)
#define WRITING(fp, fail) do{\
		if( !(PerlIOBase(fp)->flags & PERLIO_F_CANWRITE)) return (fail);\
		PerlIOBase(fp)->flags &= ~PERLIO_F_RDBUF;\
	} while(0)

#define IOC(f) (PerlIOSelf((f), PerlIOCode))


typedef struct{
	struct _PerlIO base;
	CV* cv;
	SV* arg;


	SV* buf;
	Off_t offset;
} PerlIOCode;


static PerlIO*
PerlIOCode_open(pTHX_ PerlIO_funcs *tab,
			PerlIO_list_t *layers, IV n, const char *mode,
			int fd, int imode, int perm,
			PerlIO *f, int narg, SV **args){
	PERL_UNUSED_ARG(layers);
	PERL_UNUSED_ARG(n);
	PERL_UNUSED_ARG(fd);
	PERL_UNUSED_ARG(imode);
	PERL_UNUSED_ARG(perm);

	/* 0 < narg < 3 */
	assert(narg > 0);
	if(narg > 2){
		/* too many arguments */
		SETERRNO(EINVAL, LIB_INVARG);
		return NULL;
	}

	if(f){
		PerlIO_close(f);
	}
	else{
		f = PerlIO_allocate(aTHX);
	}

	if ( (f = PerlIO_push(aTHX_ f, tab, mode, args[0])) ) {
		PerlIOBase(f)->flags |= PERLIO_F_OPEN;

		if(narg == 2){
			IOC(f)->arg = SvREFCNT_inc_simple_NN(args[1]);
		}
	}
	return f;
}

static IV
PerlIOCode_pushed(pTHX_ PerlIO * f, const char *mode, SV * arg, PerlIO_funcs * tab){
	PerlIOCode* ioc = IOC(f);

	if(arg && SvOK(arg)){
		HV* stash;
		GV* gv;
		ioc->cv = sv_2cv(arg, &stash, &gv, TRUE);

		assert(ioc != NULL);
/*
		if(ioc->cv == NULL){
			SETERRNO(ENOENT,RMS_FNF);
			return NULL;
		}
*/
		SvREFCNT_inc_simple_void_NN(ioc->cv);
	}
	else{
		SETERRNO(EINVAL, LIB_INVARG);
		return -1; /* fail */
	}

	ioc->buf = newSVpvs("");
	ioc->offset = 0;

	return PerlIOBase_pushed(aTHX_ f, mode, Nullsv, tab);
}

static IV
PerlIOCode_popped(pTHX_ PerlIO * f){
	PerlIOCode *ioc = IOC(f);

	SvREFCNT_dec(ioc->cv);
	ioc->cv = Nullcv;

	SvREFCNT_dec(ioc->buf);
	ioc->buf = Nullsv;

	SvREFCNT_dec(ioc->arg);
	ioc->arg = Nullsv;

	return PerlIOBase_popped(aTHX_ f);
}


static SSize_t
PerlIOCode_write(pTHX_ PerlIO *f, const void *vbuf, Size_t count){
	dVAR; dSP;

	PerlIOCode* ioc = IOC(f);

	WRITING(f, -1);

	PUSHMARK(SP);
	if(ioc->arg) XPUSHs(ioc->arg);
	sv_setpvn(ioc->buf, vbuf, count);
	XPUSHs(ioc->buf);
	PUTBACK;

	call_sv((SV*)ioc->cv, G_VOID | G_DISCARD);

	return count;
}

static IV
PerlIOCode_fill(pTHX_ PerlIO* f){
	dVAR; dSP;
	PerlIOCode* ioc = IOC(f);
	SV* result;

	READING(f, -1);

	if(PerlIOBase(f)->flags & PERLIO_F_EOF){
		return -1;
	}

	ENTER;
	SAVETMPS;

	PUSHMARK(SP);
	if(ioc->arg){
		XPUSHs(ioc->arg);
		PUTBACK;
	}

	call_sv((SV*)ioc->cv, G_SCALAR);

	SPAGAIN;
	result = POPs;
	PUTBACK;

	if(SvOK(result)){
		sv_copypv(ioc->buf, result);
		PerlIOBase(f)->flags |= PERLIO_F_RDBUF;
	}
	else{
		PerlIOBase(f)->flags |= PERLIO_F_EOF;
		PerlIOBase(f)->flags &= ~PERLIO_F_RDBUF;
		sv_setpvn(ioc->buf, "", 0);
	}

	ioc->offset = 0;

	FREETMPS;
	LEAVE;

	return SvCUR(ioc->buf) ? 0 : -1;
}

static STDCHAR*
PerlIOCode_get_base(pTHX_ PerlIO* f){
	return (STDCHAR*)SvPVX(IOC(f)->buf);
}

static Size_t
PerlIOCode_bufsiz(pTHX_ PerlIO* f){
	return SvCUR(IOC(f)->buf);
}

static STDCHAR*
PerlIOCode_get_ptr(pTHX_ PerlIO* f){
	return (STDCHAR*)SvPVX(IOC(f)->buf) + IOC(f)->offset;
}

static SSize_t
PerlIOCode_get_cnt(pTHX_ PerlIO* f){
	return SvCUR(IOC(f)->buf) - IOC(f)->offset;
}

static void
PerlIOCode_set_ptrcnt(pTHX_ PerlIO* f, STDCHAR* ptr, SSize_t cnt){
	PERL_UNUSED_ARG(ptr);

	IOC(f)->offset = SvCUR(IOC(f)->buf) - cnt;
}

PERLIO_FUNCS_DECL(PerlIO_code) = {
	sizeof(PerlIO_funcs),
	"Code",
	sizeof(PerlIOCode),
	PERLIO_K_BUFFERED | PERLIO_K_RAW | PERLIO_K_MULTIARG,
	PerlIOCode_pushed,
	PerlIOCode_popped,
	PerlIOCode_open,
	PerlIOBase_binmode,
	NULL,
	NULL,
	NULL,
	NULL,
	NULL,
	PerlIOCode_write,
	NULL,
	NULL,
	NULL,
	NULL,
	PerlIOCode_fill,
	NULL,
	NULL,
	NULL,
	NULL,
	PerlIOCode_get_base,
	PerlIOCode_bufsiz,
	PerlIOCode_get_ptr,
	PerlIOCode_get_cnt,
	PerlIOCode_set_ptrcnt
};


MODULE = PerlIO::code		PACKAGE = PerlIO::code		

PROTOTYPES: DISABLE

BOOT:
	PerlIO_define_layer(aTHX_ &PerlIO_code);