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

#define Tcl_new(class) Tcl_CreateInterp()
#define Tcl_result(interp) interp->result
#define Tcl_DESTROY(interp) Tcl_DeleteInterp(interp)

typedef Tcl_Interp *Tcl;
typedef AV *Tcl__Var;

int Tcl_PerlCallWrapper(clientData, interp, argc, argv)
ClientData clientData;
Tcl_Interp *interp;
int argc;
char **argv;
{
    dSP;
    AV *av = (AV *) clientData;
    I32 count;
    SV *sv;
    int rc;

    /*
     * av = [$perlsub, $realclientdata, $interp, $deleteProc]
     * (where $deleteProc is optional but we don't need it here anyway)
     */

    if (AvFILL(av) != 2 && AvFILL(av) != 3)
	croak("bad clientdata argument passed to Tcl_PerlCallWrapper");

    ENTER;
    SAVETMPS;

    PUSHMARK(sp);
    EXTEND(sp, argc + 2);
    PUSHs(sv_mortalcopy(*av_fetch(av, 1, FALSE)));
    PUSHs(sv_mortalcopy(*av_fetch(av, 2, FALSE)));
    while (argc--)
	PUSHs(sv_2mortal(newSVpv(*argv++, 0)));
    PUTBACK;
    count = perl_call_sv(*av_fetch(av, 0, FALSE), G_SCALAR);
    SPAGAIN;
    if (count != 1)
	croak("perl sub bound to Tcl proc didn't return exactly 1 argument");

    sv = POPs;
    PUTBACK;
    
    rc = SvOK(sv) ? TCL_OK : TCL_ERROR;
    if (rc == TCL_OK)
	Tcl_SetResult(interp, SvPV(sv, na), TCL_VOLATILE);
    /*
     * If the routine returned undef, it indicates that it has done the
     * SetResult itself and that we should return TCL_ERROR
     */

    FREETMPS;
    LEAVE;
    return rc;
}

void
Tcl_PerlCallDeleteProc(clientData)
ClientData clientData;
{
    AV *av = (AV *) clientData;
    
    /*
     * av = [$perlsub, $realclientdata, $interp, $deleteProc]
     * (where $deleteProc is optional but we don't need it here anyway)
     */

    if (AvFILL(av) == 3)
    {
	dSP;

	PUSHMARK(sp);
	EXTEND(sp, 1);
	PUSHs(sv_mortalcopy(*av_fetch(av, 1, FALSE)));
	PUTBACK;
	(void) perl_call_sv(*av_fetch(av, 3, FALSE), G_SCALAR|G_DISCARD);
    }
    else if (AvFILL(av) != 2)
	croak("bad clientdata argument passed to Tcl_PerlCallDeleteProc");

    SvREFCNT_dec((AV *) clientData);
}

void
prepare_Tcl_result(interp, caller)
Tcl interp;
char *caller;
{
    dSP;
    int argc;
    char **argv, **tofree;
    
    if (!GIMME)
	PUSHs(sv_2mortal(newSVpv(interp->result, 0)));
    else
    {
	if (Tcl_SplitList(interp, interp->result, &argc, &argv) != TCL_OK)
	    croak("%s called in list context did not return a valid Tcl list",
		  caller);
	
	tofree = argv;
	EXTEND(sp, argc);
	while (argc--)
	    PUSHs(sv_2mortal(newSVpv(*argv++, 0)));
	free((char *) tofree);
    }
    PUTBACK;
    return;
}

MODULE = Tcl	PACKAGE = Tcl	PREFIX = Tcl_

Tcl
Tcl_new(class = "Tcl")
	char *	class

char *
Tcl_result(interp)
	Tcl	interp

void
Tcl_Eval(interp, script)
	Tcl	interp
	SV *	script
	SV *	interpsv = ST(0);
    PPCODE:
	(void) sv_2mortal(SvREFCNT_inc(interpsv));
	PUTBACK;
	Tcl_ResetResult(interp);
	if (Tcl_Eval(interp, SvPV(sv_mortalcopy(script), na)) != TCL_OK)
	    croak(interp->result);
	prepare_Tcl_result(interp, "Tcl::Eval");
	SPAGAIN;

void
Tcl_EvalFile(interp, filename)
	Tcl	interp
	char *	filename
	SV *	interpsv = ST(0);
    PPCODE:
	(void) sv_2mortal(SvREFCNT_inc(interpsv));
	PUTBACK;
	Tcl_ResetResult(interp);
	if (Tcl_EvalFile(interp, filename) != TCL_OK)
	    croak(interp->result);
	prepare_Tcl_result(interp, "Tcl::EvalFile");
	SPAGAIN;

void
Tcl_GlobalEval(interp, script)
	Tcl	interp
	SV *	script
	SV *	interpsv = ST(0);
    PPCODE:
	(void) sv_2mortal(SvREFCNT_inc(interpsv));
	PUTBACK;
	Tcl_ResetResult(interp);
	if (Tcl_GlobalEval(interp, SvPV(sv_mortalcopy(script), na)) != TCL_OK)
	    croak(interp->result);
	prepare_Tcl_result(interp, "Tcl::GlobalEval");
	SPAGAIN;

void
Tcl_EvalFileHandle(interp, handle)
	Tcl	interp
	FILE *	handle
	int	append = 0;
	SV *	interpsv = ST(0);
	SV *	sv = sv_newmortal();
	char *	s = NO_INIT
    PPCODE:
	(void) sv_2mortal(SvREFCNT_inc(interpsv));
	PUTBACK;
	while (s = sv_gets(sv, handle, append))
	{
	    if (!Tcl_CommandComplete(s))
		append = 1;
	    else
	    {
		Tcl_ResetResult(interp);
		if (Tcl_Eval(interp, s) != TCL_OK)
		    croak(interp->result);
		append = 0;
	    }
	}
	if (append)
	    croak("unexpected end of file in Tcl::EvalFileHandle");
	prepare_Tcl_result(interp, "Tcl::EvalFileHandle");
	SPAGAIN;

void
Tcl_call(interp, proc, ...)
	Tcl		interp
	SV *		proc
	Tcl_CmdInfo	cmdinfo = NO_INIT
	int		i = NO_INIT
	static char **	argv = NO_INIT
	static int	argv_cursize = 0;
    PPCODE:
	if (argv_cursize == 0)
	{
	    argv_cursize = (items < 16) ? 16 : items;
	    New(666, argv, argv_cursize, char *);
	}
	else if (argv_cursize < items)
	{
	    argv_cursize = items;
	    Renew(argv, argv_cursize, char *);
	}
	SP++;			/* bypass the interp argument */
	for (i = 0; i < items - 1; i++)
	{
	    /*
	     * Use proc as a spare SV* variable: macro SvPV evaluates
	     * its arguments more than once.
	     */
	    proc = sv_mortalcopy(*++SP);
	    argv[i] = SvPV(proc, na);
	}
	argv[items - 1] = (char *) 0;
	if (!Tcl_GetCommandInfo(interp, argv[0], &cmdinfo))
	    croak("Tcl procedure not found");
	SP -= items;
	PUTBACK;
	Tcl_ResetResult(interp);
	if ((*cmdinfo.proc)(cmdinfo.clientData,interp,items-1, argv) != TCL_OK)
	    croak(interp->result);
	prepare_Tcl_result(interp, "Tcl::call");
	SPAGAIN;

void
Tcl_DESTROY(interp)
	Tcl	interp

void
Tcl_Init(interp)
	Tcl	interp
    CODE:
	if (Tcl_Init(interp) != TCL_OK)
	    croak(interp->result);

void
Tcl_CreateCommand(interp,cmdName,cmdProc,clientData=&sv_undef,deleteProc=Nullsv)
	Tcl	interp
	char *	cmdName
	SV *	cmdProc
	SV *	clientData
	SV *	deleteProc
    CODE:
	if (SvIOK(cmdProc))
	    Tcl_CreateCommand(interp, cmdName, (Tcl_CmdProc *) SvIV(cmdProc),
			      (ClientData) SvIV(clientData), NULL);
	else
	{
	    AV *av = (AV *) SvREFCNT_inc((SV *) newAV());
	    av_store(av, 0, newSVsv(cmdProc));
	    av_store(av, 1, newSVsv(clientData));
	    av_store(av, 2, newSVsv(ST(0)));
	    if (deleteProc)
		av_store(av, 3, newSVsv(deleteProc));
	    Tcl_CreateCommand(interp, cmdName, Tcl_PerlCallWrapper,
			      (ClientData) av, Tcl_PerlCallDeleteProc);
	}
	ST(0) = &sv_yes;
	XSRETURN(1);

void
Tcl_SetResult(interp, str)
	Tcl	interp
	char *	str
    CODE:
	Tcl_SetResult(interp, str, TCL_VOLATILE);
	ST(0) = ST(1);
	XSRETURN(1);

void
Tcl_AppendElement(interp, str)
	Tcl	interp
	char *	str

void
Tcl_ResetResult(interp)
	Tcl	interp


char *
Tcl_AppendResult(interp, ...)
	Tcl	interp
	int	i = NO_INIT
    CODE:
	for (i = 1; i <= items; i++)
	    Tcl_AppendResult(interp, SvPV(ST(i), na), NULL);
	RETVAL = interp->result;
    OUTPUT:
	RETVAL

int
Tcl_DeleteCommand(interp, cmdName)
	Tcl	interp
	char *	cmdName
    CODE:
	RETVAL = Tcl_DeleteCommand(interp, cmdName) == 0;
    OUTPUT:
	RETVAL

void
Tcl_SplitList(interp, str)
	Tcl		interp
	char *		str
	int		argc = NO_INIT
	char **		argv = NO_INIT
	char **		tofree = NO_INIT
    PPCODE:
	if (Tcl_SplitList(interp, str, &argc, &argv) == TCL_OK)
	{
	    tofree = argv;
	    EXTEND(sp, argc);
	    while (argc--)
		PUSHs(sv_2mortal(newSVpv(*argv++, 0)));
	    free((char *) tofree);
	}

char *
Tcl_SetVar(interp, varname, value, flags = 0)
	Tcl	interp
	char *	varname
	char *	value
	int	flags

char *
Tcl_SetVar2(interp, varname1, varname2, value, flags = 0)
	Tcl	interp
	char *	varname1
	char *	varname2
	char *	value
	int	flags

char *
Tcl_GetVar(interp, varname, flags = 0)
	Tcl	interp
	char *	varname
	int	flags

char *
Tcl_GetVar2(interp, varname1, varname2, flags = 0)
	Tcl	interp
	char *	varname1
	char *	varname2
	int	flags

int
Tcl_UnsetVar(interp, varname, flags = 0)
	Tcl	interp
	char *	varname
	int	flags
    CODE:
	RETVAL = Tcl_UnsetVar(interp, varname, flags) == TCL_OK;
    OUTPUT:
	RETVAL

int
Tcl_UnsetVar2(interp, varname1, varname2, flags = 0)
	Tcl	interp
	char *	varname1
	char *	varname2
	int	flags
    CODE:
	RETVAL = Tcl_UnsetVar2(interp, varname1, varname2, flags) == TCL_OK;
    OUTPUT:
	RETVAL

MODULE = Tcl		PACKAGE = Tcl::Var

char *
FETCH(av, key = NULL)
	Tcl::Var	av
	char *		key
	SV *		sv = NO_INIT
	Tcl		interp = NO_INIT
	char *		varname1 = NO_INIT
	int		flags = 0;
    CODE:
	/*
	 * This handles both hash and scalar fetches. The blessed object
	 * passed in is [$interp, $varname, $flags] ($flags optional).
	 */
	if (AvFILL(av) != 1 && AvFILL(av) != 2)
	    croak("bad object passed to Tcl::Var::FETCH");
	sv = *av_fetch(av, 0, FALSE);
	if (sv_isa(sv, "Tcl"))
	{
	    IV tmp = SvIV((SV *) SvRV(sv));
	    interp = (Tcl) tmp;
	}
	else
	    croak("bad object passed to Tcl::Var::FETCH");
	if (AvFILL(av) == 2)
	    flags = (int) SvIV(*av_fetch(av, 2, FALSE));
	varname1 = SvPV(*av_fetch(av, 1, FALSE), na);
	RETVAL = key ? Tcl_GetVar2(interp, varname1, key, flags)
		     : Tcl_GetVar(interp, varname1, flags);
    OUTPUT:
	RETVAL

void
STORE(av, str1, str2 = NULL)
	Tcl::Var	av
	char *		str1
	char *		str2
	SV *		sv = NO_INIT
	Tcl		interp = NO_INIT
	char *		varname1 = NO_INIT
	int		flags = 0;
    CODE:
	/*
	 * This handles both hash and scalar stores. The blessed object
	 * passed in is [$interp, $varname, $flags] ($flags optional).
	 */
	if (AvFILL(av) != 1 && AvFILL(av) != 2)
	    croak("bad object passed to Tcl::Var::STORE");
	sv = *av_fetch(av, 0, FALSE);
	if (sv_isa(sv, "Tcl"))
	{
	    IV tmp = SvIV((SV *) SvRV(sv));
	    interp = (Tcl) tmp;
	}
	else
	    croak("bad object passed to Tcl::Var::STORE");
	if (AvFILL(av) == 2)
	    flags = (int) SvIV(*av_fetch(av, 2, FALSE));
	varname1 = SvPV(*av_fetch(av, 1, FALSE), na);
	/*
	 * hash stores have key str1 and value str2
	 * scalar ones just use value str1
	 */
	if (str2)
	    (void) Tcl_SetVar2(interp, varname1, str1, str2, flags);
	else
	    (void) Tcl_SetVar(interp, varname1, str1, flags);