The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*============================================================================
 * Ruby.xs
 * Inline::Ruby method bindings.
 *
 * Here's a quick map of this file:
 *
 * XS:
 * Inline::Ruby::rb_eval			===> evaluates ruby code
 * Inline::Ruby::rb_call_function		\
 * Inline::Ruby::rb_call_class_method		 ====> call_ruby_method()
 * Inline::Ruby::rb_call_instance_method	/
 *
 * C:
 * call_ruby_method
 *   |__ my_error_wrapper
 *   |  |__ rb_funcall2				===> calls Ruby method
 *   |  \__ rb_iterate
 *   |     |__ my_iter_it
 *   |     |  \__ rb_funcall2			===> calls Ruby method
 *   |     |
 *   |     \__ my_iter_bl			===> calls Perl iterator
 *   |
 *   \__ my_error_trap				===> throws Perl exception
 *==========================================================================*/

/* perl stuff */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "rb2pl.h"
#ifdef	EXPOSE_PERL
# include "perlmodule.h"
#endif


/*============================================================================
 * This macro creates and fills a ruby array from the Perl call stack. It
 * relies on the Perl dXSARGS() macro having been called before it; XS does
 * this automatically. It also requires NUM_FIXED_ARGS to be defined.
 *==========================================================================*/
#define INIT_RUBY_ARGV(name) {						     \
    int i;								     \
    name = rb_ary_new2(items >= NUM_FIXED_ARGS ? items-NUM_FIXED_ARGS : 0);  \
    for (i = NUM_FIXED_ARGS; i < items; i++) {				     \
	VALUE tmp = pl2rb(ST(i));					     \
	rb_ary_push(name, tmp);						     \
    }									     \
}

/*============================================================================
 * These macros either flatten the return value (converted into Perl
 * variables) onto the Perl stack, or return it preserved.
 *==========================================================================*/
#define FLATTEN_RETVAL(name) {						     \
    if (GIMME_V == G_ARRAY && SvROK(name) && 				     \
	    SvTYPE(SvRV(name)) == SVt_PVAV)				     \
    {									     \
	AV* av = (AV*)SvRV(name);					     \
	int len = av_len(av) + 1;					     \
	int i;								     \
	for (i=0; i<len; i++) {						     \
	    XPUSHs(sv_2mortal(av_shift(av)));				     \
	}								     \
    }									     \
    else {								     \
	XPUSHs(name);							     \
    }									     \
}
#define PRESERVE_RETVAL(name) XPUSHs(name)

/* The PerlException class */
VALUE rb_ePerlException;
static void
Init_PerlException() {
    rb_ePerlException = rb_define_class("PerlException", rb_eStandardError);
}

/*============================================================================
 * Initializes the Ruby interpreter. This is copied for the most part from
 * main.c in the Ruby sources.
 *==========================================================================*/
#ifdef	CREATE_RUBY
extern VALUE rb_progname;
extern VALUE rb_argv;
extern VALUE rb_argv0;
static void
do_rbinit() {
    char *argv[] = { "ruby" };
    int argc = sizeof(argv)/sizeof(argv[0]);

#ifdef RUBY_INIT_STACK
    RUBY_INIT_STACK;
#endif
    /* set up the initial ruby interpreter */
    ruby_init();

    /* Set the program name, argv, and argv0 to the right things */
    ruby_script("Inline::Ruby");
    rb_argv0 = rb_str_new2(argv[0]);
    ruby_set_argv(argc, argv);

    /* Allow loading of dynamic libraries */
    ruby_init_loadpath();
    /* #if-ing out because maybe no longer needed and not supported in
     * recent MRIs:
     *
     * http://my.opera.com/subjam/blog/embedding-ruby-in-c-programs
     *
     * */
#if 0
    Init_ext();
#endif

    /* Set up our own types */
    Init_PerlException();
    Init_PerlProc();
}
#endif

/*============================================================================
 * This is an iterator method, which just calls the real Ruby block. The
 * callback block is invoked whenever the Ruby code calls "yield". Look at the
 * definition of my_iter_bl(), below.
 *==========================================================================*/
static VALUE
my_iter_it(fake)
    VALUE fake;
{
    VALUE obj, method, args;

    Printf(("Note: in my_iter_it(%p)\n", (void *) fake));
    Printf(("Type: TYPE(fake) = %i\n", TYPE(fake)));
    obj		= rb_ary_entry(fake, 0);
    method	= rb_ary_entry(fake, 1);
    args	= rb_ary_entry(fake, 2);

    Printf(("============================\n"));
    Printf(("About to call rb_funcall2...\n"));
    Printf(("obj = %s (%i)\n", STR2CSTR(rb_inspect(obj)), TYPE(obj)));
    Printf(("method = %s (%i)\n", STR2CSTR(rb_inspect(method)), TYPE(method)));
    Printf(("args = %s (%i)\n", STR2CSTR(rb_inspect(args)), TYPE(args)));
    Printf(("============================\n"));
    return rb_funcall2(obj, rb_intern(STR2CSTR(method)),
		       RARRAY_LEN(args), RARRAY_PTR(args));
}

/*
 * This function was contributed by mauke.
 */
static void my_do_chomp(SV * sv)
{
    char * p;
    STRLEN n;

    p = SvPV_force(sv, n);
    if (n && p[n - 1] == '\n')
    {
        n--; p[n] = '\0';
        SvCUR_set(sv, n);
    }
}

/*============================================================================
 * This is the iterator block invoked whenever the Ruby code calls yield(). It
 * is passed an optional value each time it's called (opt), which contains the
 * Perl subroutine ref to forward the call to. The arguments to the block come
 * in 'res'.
 *==========================================================================*/
static VALUE
my_iter_bl(res, cv)
    VALUE	res;
    SV*		cv;
{
    dSP;

    SV* args;
    I32 count;			/* how many values returned on Perl stack */
    I32 ax;
    VALUE rb_retval;

    Printf(("Note: inside my_iter_bl...\n"));
    Printf(("Note: TYPE(res) == %i\n", TYPE(res)));

    Printf(("Note: the ruby args are: %s\n", STR2CSTR(rb_inspect(res))));

    args = rb2pl(res);	/* convert the Ruby args to Perl args */

    ENTER;
    SAVETMPS;
    PUSHMARK(SP);

#ifdef	FLATTEN_CALLBACK_ARGS
    if (SvROK(args) && SvTYPE(SvRV(args)) == SVt_PVAV) {
	AV* av = (AV*) SvRV(args);
	int len = av_len(av) + 1;
	int i;
	for (i=0; i<len; i++) {
	    XPUSHs(sv_2mortal(av_shift(av)));
	}
    }
    else
#endif
	XPUSHs(sv_2mortal(args));

    PUTBACK;

    count = call_sv(cv, G_SCALAR | G_EVAL | G_KEEPERR);

    SPAGAIN;

    /* Perl stack magic (to enable using ST(n)) */
    SP -= count;
    ax = (SP - PL_stack_base) + 1;

    /* Check the eval first */
    if (SvTRUE(ERRSV)) {
	/* Must clean up the stack if we died with G_SCALAR */
	POPs;

	/* stringify the Perl error into the Ruby error */
	my_do_chomp(ERRSV);
	rb_raise(rb_ePerlException, "%s", SvPV_nolen(ERRSV));
	return Qnil;	/* not reached */
    }

    if (count == 0)
	rb_retval = Qnil;
    else if (count == 1)
	rb_retval = pl2rb(ST(0));
    else {
	int i;
	rb_retval = rb_ary_new2(count);
	for (i=0; i<count; i++) {
	    rb_ary_push(rb_retval, pl2rb(ST(i)));
	}
    }

    PUTBACK;
    FREETMPS;
    LEAVE;

    return rb_retval;
}

/*============================================================================
 * This is called whenever there's an uncaught exception in some Ruby code
 * being run by Inline::Ruby. It sets Perl's global error object to an
 * Inline::Ruby::Exception object. It stringifies nicely into the same string
 * as Ruby's error message. You can also call methods on it. See the doc.
 *==========================================================================*/
static VALUE
my_error_trap(arg, error)
    VALUE	arg;
    VALUE	error;
{
    dSP;
    HV*	wrapper;
    SV*	ref;

    wrapper = newHV();

    Printf(("Note: Ruby threw an exception!!!\n"));
    Printf(("Note: %s\n", STR2CSTR(rb_inspect(error))));

    /* Create a wrapper */
    (void)hv_store(wrapper, "_rb_exc", 7, newSViv((IV)error), 0);
    ref = newRV_noinc((SV*)wrapper);

    /* Throw a Perl exception */
    ENTER;
    SAVETMPS;

    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newSVpv("Inline::Ruby::Exception", 0)));
    XPUSHs(sv_2mortal(ref));
    PUTBACK;

    call_pv("Inline::Ruby::Exception::new", G_VOID | G_DISCARD);

    FREETMPS;
    LEAVE;

    /* not reached */
    return Qnil;
}

/*============================================================================
 * This wrapper is called by call_ruby_method(), to protect blocks of code
 * from uncaught exceptions. Ruby walks up the exception tree looking for an
 * exception handler, and will walk off the tree into segfault land if you
 * don't provide a top-level rescue block. So we do.
 *==========================================================================*/
static VALUE
my_error_wrapper(arg)
    VALUE	arg;
{
    VALUE	obj;
    char*	method;
    SV*		iter;
    VALUE	argv;
    VALUE	retv;

    /* Extract the arguments */
    obj = rb_ary_entry(arg, 0);
    {
        VALUE method_obj = rb_ary_entry(arg, 1);
        method = STR2CSTR(method_obj);
    }
    iter = (SV*)rb_ary_entry(arg, 2);
    argv = rb_ary_entry(arg, 3);

    /* If 'iter' points to a Perl subroutine, run an iterator instead of just
     * a boring old function. */
    if (iter && SvROK(iter) && SvTYPE(SvRV(iter)) == SVt_PVCV) {
	VALUE it_args;
	it_args = rb_ary_new3(3, obj, rb_str_new2(method), argv);
	Printf(("Note: calling rb_interate(%p, %p, %p, %p)\n",
	       my_iter_it, (void *) it_args, my_iter_bl, iter));
	retv = rb_iterate(&my_iter_it, it_args, &my_iter_bl, (VALUE)iter);
    }
    else {
	Printf(("calling func\n"));
	retv = rb_funcall2(obj, rb_intern(method),
			   RARRAY_LEN(argv), RARRAY_PTR(argv));
    }
    /* If we get here, there were no exceptions */
    Printf(("No exceptions thrown, clearing ERRSV!\n"));
    sv_setpvf(ERRSV, "%s", "");
    return retv;
}

/* This serves to wrap the eval() in a rescue() block */
static VALUE
my_eval_string(str)
    VALUE	str;
{
    return rb_eval_string(STR2CSTR(str));
}

/*============================================================================
 * This is my generic ruby method caller. It will call either global
 * functions, class methods, or instance methods. There's a simple way to tell
 * which is which. The first argument is either a string (the name of the
 * class), an object (the object to call the method on), or undef (indicates a
 * global function). The second is the name of the method to call. The third
 * is an optional iterator function.
 *==========================================================================*/
static SV*
call_ruby_method(obj, method, iter, argv)
    VALUE	obj;
    char*	method;
    SV*		iter;
    VALUE	argv;
{
    VALUE	wrap_argv;
    VALUE	rb_retval;

    Printf(("call_ruby_method(%p, '%s', %p)\n", (void *) obj, method, iter));

    /* If obj is a string, it is the name the class upon which to call the
     * class method. */
    if (TYPE(obj) == T_STRING) {
	Printf(("call_ruby_method: obj is a class; getting a handle...\n"));
	obj = rb_const_get(rb_cObject, rb_intern(STR2CSTR(obj)));
    }

    /* Because we want to "wrap" the call in a rescue block, we use rb_rescue.
     * The block we call is yet another wrapper, which just forwards the call
     * to the real function: */
    wrap_argv = rb_ary_new3(4, obj, rb_str_new2(method), (VALUE)iter, argv);
    rb_retval = rb_rescue2(&my_error_wrapper, wrap_argv, &my_error_trap, Qnil,
			   rb_eException, 0);

    if (!rb_retval) {
	croak("Error: rb_funcall2() returned a NULL C pointer");
	return &PL_sv_undef;	/* not reached */
    }

#ifdef	CHECK_CONTEXT
    if (GIMME_V == G_VOID)
	return &PL_sv_undef;
#endif

    return rb2pl(rb_retval);
}

MODULE = Inline::Ruby	PACKAGE = Inline::Ruby	PREFIX = my_

BOOT:
#ifdef	CREATE_RUBY
do_rbinit();
rb_gc_start(); /* important */
rb_funcall(rb_stdout, rb_intern("sync="), 1, 1);
#endif

PROTOTYPES: DISABLE

#=============================================================================
# This is called to evaluate ruby code (duh!). It uses rb_rescue to trap any
# compile errors raised by the interpreter. If an exception is thrown we
# return an undef, and set the global variable "$@".
#=============================================================================
int
config_var(str)
	char*	str
    CODE:
	if (strEQ(str, "CHECK_CONTEXT"))
#ifdef	CHECK_CONTEXT
	    RETVAL = 1;
#else
	    RETVAL = 0;
#endif
	else if (strEQ(str, "FLATTEN_ARRAYS"))
#ifdef	FLATTEN_ARRAYS
	    RETVAL = 1;
#else
	    RETVAL = 0;
#endif
	else if (strEQ(str, "FLATTEN_CALLBACK_ARGS"))
#ifdef	FLATTEN_CALLBACK_ARGS
	    RETVAL = 1;
#else
	    RETVAL = 0;
#endif
	else {
	    if (PL_dowarn)
		warn("Inline::Ruby::config_var: unknown config var '%s'", str);
	    XSRETURN_UNDEF;
	}
    OUTPUT:
	RETVAL

#=============================================================================
# This is called to evaluate ruby code (duh!). It uses rb_rescue to trap any
# compile errors raised by the interpreter. If an exception is thrown we
# return an undef, and set the global variable "$@".
#=============================================================================
void
my_rb_eval(str)
	char*	str
    PREINIT:
	SV*	pl_retval;
    PPCODE:
	Printf(("About to evaluate some Ruby code:\n"));
        Printf(("%s\n", str));
        Printf(("__END__"));
#ifdef rb_set_errinfo
    rb_set_errinfo(Qnil); /* reset GET_THREAD()->errinfo */
#endif
	pl_retval = rb2pl(rb_rescue2(&my_eval_string, rb_str_new2(str),
				  &my_error_trap, Qnil, rb_eException, 0));
	Printf(("Done.\n"));
#if defined CHECK_CONTEXT && defined FLATTEN_ARRAYS
	FLATTEN_RETVAL(pl_retval);
#else
	PRESERVE_RETVAL(pl_retval);
#endif

#=============================================================================
# This sub calls a global ruby method. It takes the name of the method to run,
# an optional iterator, and any arguments to the method.
#=============================================================================
#undef	NUM_FIXED_ARGS
#define	NUM_FIXED_ARGS 1
void
my_rb_call_function(FNAME, ...)
	char*	FNAME
    PREINIT:
	VALUE	argv;
	SV*	pl_retval;
    PPCODE:
	Printf(("rb_call_function(\"%s\")\n", FNAME));
	INIT_RUBY_ARGV(argv);
	pl_retval = call_ruby_method(Qnil, FNAME, NULL, argv);
#if defined CHECK_CONTEXT && defined FLATTEN_ARRAYS
	FLATTEN_RETVAL(pl_retval);
#else
	PRESERVE_RETVAL(pl_retval);
#endif

#=============================================================================
# This is called whenever you need to call a class method. It takes the name
# of the class, the method name, an iterator block, and any arguments to the
# method. If the iterator block is not a reference to a Perl subroutine, it is
# not passed to the ruby method.
#=============================================================================
#undef  NUM_FIXED_ARGS
#define NUM_FIXED_ARGS 2
void
my_rb_call_class_method(KLASS, mname, ...)
	char*	KLASS
	char*	mname
    PREINIT:
	VALUE	klass;
	VALUE	argv;
	SV*	pl_retval;
    PPCODE:
	Printf(("rb_call_class_method('%s', '%s', ...)\n",KLASS,mname));

	INIT_RUBY_ARGV(argv);
	klass = rb_str_new2(KLASS);
	pl_retval = call_ruby_method(klass, mname, NULL, argv);
#if defined CHECK_CONTEXT && defined FLATTEN_ARRAYS
	FLATTEN_RETVAL(pl_retval);
#else
	PRESERVE_RETVAL(pl_retval);
#endif

#=============================================================================
# This is called whenever you need to call an instance method. It takes the
# instance of the class, the method name, an iterator block, and any arguments
# to the method. If the iterator block is not a reference to a Perl
# subroutine, it is not passed to the ruby method.
#=============================================================================
#undef  NUM_FIXED_ARGS
#define NUM_FIXED_ARGS 2
void
my_rb_call_instance_method(_inst, mname, ...)
	SV*	_inst
	char*	mname
    PREINIT:
	VALUE	inst;
	VALUE	argv;
	SV*	pl_retval;
	SV*	iter;
    PPCODE:
	Printf(("rb_call_instance_method(%p, '%s', ...)\n",
		_inst, mname));

	if (isa_InlineRubyWrapper(_inst)) {
	    inst = UNWRAP_RUBY_OBJ(_inst);
	    iter = INLINE_MAGIC(_inst)->iter;
	    Printf(("inst (%p) successfully passed the PVMG test\n", (void *) inst));
	}
	else {
	    croak("Object is not a wrapped Inline::Ruby::Object object");
	    XSRETURN_EMPTY;
	}

	INIT_RUBY_ARGV(argv);
	pl_retval = call_ruby_method(inst, mname, iter, argv);
#if defined CHECK_CONTEXT && defined FLATTEN_ARRAYS
	FLATTEN_RETVAL(pl_retval);
#else
	PRESERVE_RETVAL(pl_retval);
#endif

#=============================================================================
# This sub calls a global ruby method. It takes the name of the method to run,
# an optional iterator, and any arguments to the method.
#=============================================================================
SV*
my_rb_iter(obj, iter=NULL)
	SV*	obj
	SV*	iter
    CODE:
	/* Case 1: obj is an instance method */
	if (items == 2 && isa_InlineRubyWrapper(obj)) {
	    RETVAL = rb2pl(pl2rb(obj));	/* deep copy */
	    INLINE_MAGIC(RETVAL)->iter = iter;
	    SvREFCNT_inc(iter);
	}
	/* Case 2: obj is a class name */
	else if (items == 2 && SvTYPE(obj) == SVt_PV) {
	    RETVAL = new_InlineRubyWrapper(rb_str_new2(SvPV_nolen(obj)), iter);
	}
	else if (items == 1)
	    RETVAL = new_InlineRubyWrapper(Qnil, obj);	/* pass sub in obj */
	else
	    RETVAL = new_InlineRubyWrapper(Qnil, iter);
    OUTPUT:
	RETVAL

MODULE = Inline::Ruby	PACKAGE = Inline::Ruby::Object

void
DESTROY(obj)
        SV*	obj
    CODE:
	if (isa_InlineRubyWrapper(obj)) {
        /*
        XXX - this somehow conflicts with free_InlineRubyWrapper,
        so it's commented out until we find a way to get them work together:

 	    VALUE rb_object = UNWRAP_RUBY_OBJ(obj);
	    */
	    /* talk to the ruby garbage collector */
	}

MODULE = Inline::Ruby	PACKAGE = Inline::Ruby::Exception

#define RETRIEVE_CACHE(m) { \
    SV **svp = hv_fetch(wrapper, m, strlen(m), FALSE); \
    if (svp)                                           \
        XSRETURN_PV(SvPV_nolen(*svp));                 \
}

#define STORE_CACHE(m, v) (void)hv_store(wrapper, m, strlen(m), v, 0)

SV*
message(obj)
	SV*	obj
    ALIAS:
	Inline::Ruby::Exception::inspect	= 1
	Inline::Ruby::Exception::backtrace	= 2
    PREINIT:
	HV*     wrapper;
	char*	method;
    CODE:
	if (SvROK(obj) && SvTYPE(SvRV(obj)) == SVt_PVHV) {
	    wrapper = (HV*)SvRV(obj);
	}
	else {
	    croak("Not an Inline::Ruby::Exception object");
	    XSRETURN_EMPTY;
	}
	switch(ix) {
	    case 0:
		method = "message";
		RETRIEVE_CACHE(method);
		break;
	    case 1:
		method = "inspect";
		RETRIEVE_CACHE(method);
		break;
	    case 2:
		method = "backtrace";
		RETRIEVE_CACHE(method);
		break;
	    default:
		croak("Internal error in Inline::Ruby::Exception");
		XSRETURN_EMPTY;
	}
	VALUE rb_exception = (VALUE)SvIV(*hv_fetch(wrapper, "_rb_exc", 7, FALSE));
	RETVAL = rb2pl(rb_funcall(rb_exception, rb_intern(method), 0));
	STORE_CACHE(method, newSVsv(RETVAL));
    OUTPUT:
	RETVAL

SV*
type(obj)
	SV*	obj
    PREINIT:
	HV*     wrapper;
	char*   method;
    CODE:
	if (SvROK(obj) && SvTYPE(SvRV(obj)) == SVt_PVHV) {
	    wrapper = (HV*)SvRV(obj);
	}
	else {
	    croak("Not an Inline::Ruby::Exception object");
	    XSRETURN_EMPTY;
	}
	method = "type";
	RETRIEVE_CACHE(method);
	{
	    VALUE rb_exception = (VALUE)SvIV(*hv_fetch(wrapper, "_rb_exc", 7, FALSE));
	    VALUE klass = rb_funcall(rb_exception, rb_intern("class"), 0);
	    RETVAL = rb2pl(rb_funcall(klass, rb_intern("name"), 0));
	    STORE_CACHE(method, newSVsv(RETVAL));
	}
    OUTPUT:
	RETVAL

#undef RETRIEVE_CACHE
#undef STORE_CACHE