aboutsummaryrefslogtreecommitdiffstats
//=============================================================================
//
//   File : libkviperlcore.cpp
//   Creation date : Tue Jul 13 13:03:31 2004 GMT by Szymon Stefanek
//
//   This file is part of the KVIrc IRC client distribution
//   Copyright (C) 2004-2010 Szymon Stefanek (pragma at kvirc dot net)
//
//   This program is FREE software. You can redistribute it and/or
//   modify it under the terms of the GNU General Public License
//   as published by the Free Software Foundation; either version 2
//   of the License, or (at your option) any later version.
//
//   This program is distributed in the HOPE that it will be USEFUL,
//   but WITHOUT ANY WARRANTY; without even the implied warranty of
//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//   See the GNU General Public License for more details.
//
//   You should have received a copy of the GNU General Public License
//   along with this program. If not, write to the Free Software Foundation,
//   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
//=============================================================================

#include "KviModule.h"
#include "kvi_settings.h"
#include "KviLocale.h"
#include "kvi_out.h"
#include "KviWindow.h"
#include "KviApplication.h"
#include "KviKvsScript.h"
#include "KviKvsVariant.h"
#include "KviUserInput.h"
#include "KviPointerHashTable.h"

#include <QByteArray>

#ifdef DEBUG
#undef DEBUG
#endif

// I MUST say that the perl embedding process is somewhat ugly :(
// First of all the man pages are somewhat unreadable even
// for a non-novice perl user. The writer of each page assumed
// that you have already read each other page...
// Also browsing the pages with "man" is obviously out of mind
// but this can be solved by looking up some html docs on the net.
// Embedding multiple interpreters isn't that hard (after you
// have read perlembed) but to start passing parameters
// around you have to read at least perlembed, perlguts, perlxs,..
// take a look at the perlinternals and have a good trip
// around the web to find some examples for the functions
// that aren't explained enough in the pages.
// It gets even more weird when you attempt to include
// some XS functions... (what the heck is boot_DynaLoader ?).

// ... and I'm still convinced that I'm leaking memory with
// the perl values, but well ...

// anyway, once you struggled for a couple of days with all that
// stuff then you start getting things done... and it rox :)

// Note for kvirc4: perl embedding has changed between perl 5.8 and perl 5.10.
// This version should work nice with both, but warranty is 5.10-only.

#ifdef COMPILE_PERL_SUPPORT
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>

#define NEED_eval_pv
#include "ppport.h"

#include "KviKvsRunTimeContext.h"

static KviKvsRunTimeContext * g_pCurrentKvsContext = nullptr;
static bool g_bExecuteQuiet = false;
static KviCString g_szLastReturnValue("");
static QStringList g_lWarningList;

// this is why we can't have nice things -- part of perl 5.17.1+
#ifdef __cplusplus
#define dNOOP (void)0
#else
#define dNOOP extern int Perl___notused(void)
#endif

#include "xs.inc"
#endif // COMPILE_PERL_SUPPORT

// perl redefines bool :///
#ifdef bool
#undef bool
#endif

#ifdef COMPILE_PERL_SUPPORT

#include "perlcoreinterface.h"

// This should be able to be rewritten in the form:
// static PerlInterpreter *m_pInterpreter

// people ... are you mad ? ... what the heck is "my_perl" ?
#define my_perl m_pInterpreter

class KviPerlInterpreter
{
public:
	KviPerlInterpreter(const QString & szContextName);
	~KviPerlInterpreter();

protected:
	QString m_szContextName;
	PerlInterpreter * m_pInterpreter;

public:
	bool init(); // if this fails then well.. :D
	void done();
	bool execute(const QString & szCode, QStringList & args, QString & szRetVal, QString & szError, QStringList & lWarnings);
	const QString & contextName() const { return m_szContextName; };
protected:
	QString svToQString(SV * sv);
};

KviPerlInterpreter::KviPerlInterpreter(const QString & szContextName)
{
	m_szContextName = szContextName;
	m_pInterpreter = nullptr;
}

KviPerlInterpreter::~KviPerlInterpreter()
{
	done();
}

// this kinda sux :(
// It SHOULD be mentioned somewhere that
// this function is in DynaLoader.a in the perl
// distribution and you MUST link it statically.

// Update: it is no more needed as of perl 5.10, since it is
// included in the standard libperl interface.

extern "C" void boot_DynaLoader(pTHX_ CV * cv);

extern "C" void xs_init(pTHX)
{
	char * file = (char *)__FILE__;
	// boot up the DynaLoader
	newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
	// now bootstrap the KVIrc module
	// This stuff is simply cutted and pasted from xs.inc
	// since I don't really know if it's safe to call
	// something like
	//   CV * dummy;
	//   boot_KVIrc(aTHX,dummy);
	// ...
	newXS("KVIrc::echo", XS_KVIrc_echo, file);
	newXS("KVIrc::say", XS_KVIrc_say, file);
	newXS("KVIrc::warning", XS_KVIrc_warning, file);
	newXS("KVIrc::getLocal", XS_KVIrc_getLocal, file);
	newXS("KVIrc::setLocal", XS_KVIrc_setLocal, file);
	newXS("KVIrc::getGlobal", XS_KVIrc_getGlobal, file);
	newXS("KVIrc::setGlobal", XS_KVIrc_setGlobal, file);
	newXS("KVIrc::eval", XS_KVIrc_eval, file);
	newXS("KVIrc::internalWarning", XS_KVIrc_internalWarning, file);
}

bool KviPerlInterpreter::init()
{
	if(m_pInterpreter)
		done();
	const char * daArgs[] = { "yo", "-e", "0", "-w" };
	m_pInterpreter = perl_alloc();
	if(!m_pInterpreter)
		return false;
	PERL_SET_CONTEXT(m_pInterpreter);
	PL_perl_destruct_level = 1;
	perl_construct(m_pInterpreter);
	perl_parse(m_pInterpreter, xs_init, 4, (char **)daArgs, nullptr);
	QString szInitCode;

	// this part of the code seems to be unnecessary
	// even if it is created by the perl make process...
	//	"our %EXPORT_TAGS = ('all' => [qw(echo)]);\n"
	//	"our @EXPORT_OK = (qw(echo));\n"
	//	"our @EXPORT = qw();\n"
	// This is probably needed only if perl has to load
	// the XS through XSLoader ?
	// Maybe also the remaining part of the package
	// declaration could be dropped as well...
	// I just haven't tried :D

	szInitCode = QString(
	                 "{\n"
	                 "package KVIrc;\n"
	                 "require Exporter;\n"
	                 "our @ISA = qw(Exporter);\n"
	                 "1;\n"
	                 "}\n"
	                 "$g_szContext = \"%1\";\n"
	                 "$g_bExecuteQuiet = 0;\n"
	                 "$SIG{__WARN__} = sub\n"
	                 "{\n"
	                 "	my($p,$f,$l,$x);\n"
	                 "	($p,$f,$l) = caller;\n"
	                 "	KVIrc::internalWarning(\"At line \".$l.\" of Perl code: \");\n"
	                 "	KVIrc::internalWarning(join(' ',@_));\n"
	                 "}\n")
	                 .arg(m_szContextName);

	eval_pv(szInitCode.toUtf8().data(), false);
	return true;
}

void KviPerlInterpreter::done()
{
	if(!m_pInterpreter)
		return;
	PERL_SET_CONTEXT(m_pInterpreter);
	PL_perl_destruct_level = 1;
	perl_destruct(m_pInterpreter);
	perl_free(m_pInterpreter);
	m_pInterpreter = nullptr;
}

QString KviPerlInterpreter::svToQString(SV * sv)
{
	QString ret = "";
	if(!sv)
		return ret;
	STRLEN len;
	char * ptr = SvPV(sv, len);
	if(ptr)
		ret = ptr;
	return ret;
}

bool KviPerlInterpreter::execute(
    const QString & szCode,
    QStringList & args,
    QString & szRetVal,
    QString & szError,
    QStringList & lWarnings)
{
	if(!m_pInterpreter)
	{
		szError = __tr2qs_ctx("Internal error: Perl interpreter not initialized", "perl");
		return false;
	}

	g_lWarningList.clear();

	QByteArray szUtf8 = szCode.toUtf8();
	PERL_SET_CONTEXT(m_pInterpreter);

	// clear the _ array
	AV * pArgs = get_av("_", 1);
	SV * pArg = av_shift(pArgs);
	while(SvOK(pArg))
	{
		SvREFCNT_dec(pArg);
		pArg = av_shift(pArgs);
	}

	if(args.count() > 0)
	{
		// set the args in the _ arry
		av_unshift(pArgs, (I32)args.count());
		int idx = 0;
		for(auto tmp : args)
		{
			QByteArray szVal = tmp.toUtf8();
			pArg = newSVpv(szVal.data(), tmp.length());
			if(!av_store(pArgs, idx, pArg))
				SvREFCNT_dec(pArg);
			idx++;
		}
	}

	// call the code
	SV * pRet = eval_pv(szUtf8.data(), false);

	// clear the _ array again
	pArgs = get_av("_", 1);
	pArg = av_shift(pArgs);
	while(SvOK(pArg))
	{
		SvREFCNT_dec(pArg);
		pArg = av_shift(pArgs);
	}
	av_undef(pArgs);

	// get the ret value
	if(pRet)
	{
		if(SvOK(pRet))
			szRetVal = svToQString(pRet);
	}

	if(!g_lWarningList.isEmpty())
		lWarnings = g_lWarningList;

	// and the eventual error string
	pRet = get_sv("@", false);
	if(pRet)
	{
		if(SvOK(pRet))
		{
			szError = svToQString(pRet);
			if(!szError.isEmpty())
				return false;
		}
	}

	return true;
}

static KviPointerHashTable<QString, KviPerlInterpreter> * g_pInterpreters = nullptr;

static KviPerlInterpreter * perlcore_get_interpreter(const QString & szContextName)
{
	KviPerlInterpreter * i = g_pInterpreters->find(szContextName);
	if(i)
		return i;
	i = new KviPerlInterpreter(szContextName);
	if(!i->init())
	{
		delete i;
		return nullptr;
	}
	g_pInterpreters->replace(szContextName, i);
	return i;
}

static void perlcore_destroy_interpreter(const QString & szContextName)
{
	KviPerlInterpreter * i = g_pInterpreters->find(szContextName);
	if(!i)
		return;
	g_pInterpreters->remove(szContextName);
	i->done();
	delete i;
}

static void perlcore_destroy_all_interpreters()
{
	KviPointerHashTableIterator<QString, KviPerlInterpreter> it(*g_pInterpreters);

	while(it.current())
	{
		KviPerlInterpreter * i = it.current();
		i->done();
		delete i;
		++it;
	}
	g_pInterpreters->clear();
}

#endif // COMPILE_PERL_SUPPORT

static bool perlcore_module_ctrl(KviModule *, const char * cmd, void * param)
{
#ifdef COMPILE_PERL_SUPPORT
	if(kvi_strEqualCS(cmd, KVI_PERLCORECTRLCOMMAND_EXECUTE))
	{
		KviPerlCoreCtrlCommand_execute * ex = (KviPerlCoreCtrlCommand_execute *)param;
		if(ex->uSize != sizeof(KviPerlCoreCtrlCommand_execute))
			return false;
		g_pCurrentKvsContext = ex->pKvsContext;
		g_bExecuteQuiet = ex->bQuiet;
		if(ex->szContext.isEmpty())
		{
			KviPerlInterpreter * m = new KviPerlInterpreter("temporary");
			if(!m->init())
			{
				delete m;
				return false;
			}
			ex->bExitOk = m->execute(ex->szCode, ex->lArgs, ex->szRetVal, ex->szError, ex->lWarnings);
			m->done();
			delete m;
		}
		else
		{
			KviPerlInterpreter * m = perlcore_get_interpreter(ex->szContext);
			ex->bExitOk = m->execute(ex->szCode, ex->lArgs, ex->szRetVal, ex->szError, ex->lWarnings);
		}
		return true;
	}
	if(kvi_strEqualCS(cmd, KVI_PERLCORECTRLCOMMAND_DESTROY))
	{
		KviPerlCoreCtrlCommand_destroy * de = (KviPerlCoreCtrlCommand_destroy *)param;
		if(de->uSize != sizeof(KviPerlCoreCtrlCommand_destroy))
			return false;
		perlcore_destroy_interpreter(de->szContext);
		return true;
	}
#endif // COMPILE_PERL_SUPPORT
	return false;
}

static bool perlcore_module_init(KviModule *)
{
#ifdef COMPILE_PERL_SUPPORT
	g_pInterpreters = new KviPointerHashTable<QString, KviPerlInterpreter>(17, false);
	g_pInterpreters->setAutoDelete(false);
	int daArgc = 4;
	const char * daArgs[] = { "yo", "-e", "0", "-w" };
	char ** daEnv = nullptr;
	PERL_SYS_INIT3(&daArgc, (char ***)&daArgs, &daEnv);
	return true;
#else  // !COMPILE_PERL_SUPPORT
	return false;
#endif // !COMPILE_PERL_SUPPORT
}

static bool perlcore_module_cleanup(KviModule *)
{
#ifdef COMPILE_PERL_SUPPORT
	perlcore_destroy_all_interpreters();
	delete g_pInterpreters;
	g_pInterpreters = nullptr;
// ifdef workaround for #842
#ifndef COMPILE_ON_MAC
	PERL_SYS_TERM();
#endif
#endif // COMPILE_PERL_SUPPORT
	return true;
}

static bool perlcore_module_can_unload(KviModule *)
{
#ifdef COMPILE_PERL_SUPPORT
	return false;
	/* return (g_pInterpreters->count() == 0);
	 * Perlcore module can't be cleanly unloaded since PERL_SYS_INIT3 
	 * and PERL_SYS_TERM should never be called more than once (#1105)
	 */
#endif // COMPILE_PERL_SUPPORT
	return true;
}

KVIRC_MODULE(
    "PerlCore",                                                     // module name
    "4.0.0",                                                        // module version
    "Copyright (C) 2008 Szymon Stefanek (pragma at kvirc dot net)", // author & (C)
    "Perl scripting engine core",
    perlcore_module_init,
    perlcore_module_can_unload,
    perlcore_module_ctrl,
    perlcore_module_cleanup,
    "perl")