How to Embed Perl in your D program

This post takes influence, and reference from perldoc’s perlembed page.

Do you want to:

Use D from Perl?

Have a look at the Perl Inline API and write your own XS module. :)

Use a Unix program from Perl?

Read about back-quotes and about “system” and “exec” in perlfunc.

Use Perl from Perl?

Read about “do”, “eval”, “require” and “use” in perlfunc.

Use D from D?

Read about “mixin” in the D Language Reference for compile-time interpretation.
If you require run-time interpretation, rethink your design.

Use Perl from D?

Read on…

The D Perl Class Routine

This is made possible because Perl itself is written in C, the Perl library is the collection of compiled C programs, and D is binary compatible with C.

I’ll demonstrate this by showing a bastardized, non-portable version of miniperlmain.c containing the essentials of embedding. But re-written in D using a Class wrapper.

/*
 
  Perl/Interpreter.d
 
  Copyright (C) 2010 by Iain Buclaw
 
  You may distribute under the terms of either the GNU General Public
  License or the Artistic License. 
 
 */

module Perl.Interpreter;

/* For alloca, malloc, free */
private import std.c.stdlib;
 
/* flags in PL_exit_flags for nature of exit() */
enum
{
    EXIT_EXPECTED = 0x01,
    EXIT_DESTRUCT_END = 0x02, /* Run END in perl_destruct */
}

/* flags for Perl_call_* routines */
enum
{
    G_SCALAR = 0,
    G_ARRAY = 1,
    G_VOID = 128,  /* skip this bit when adding flags below */
    G_WANT = (128|1),
    G_DISCARD = 2,
    G_EVAL = 4,  /* Assume eval {} around subroutine call. */
    G_NOARGS = 8,  /* Don't construct a @_ array. */
    G_KEEPERR = 16,  /* Append errors to $@, don't overwrite it */
    G_NODEBUG = 32,  /* Disable debugging at toplevel.  */
    G_METHOD = 64,  /* Calling method. */
    G_FAKINGEVAL = 256,  /* Faking an eval context for call_sv or fold_constants */
}

extern (C)
{
    /* Perl Defines */
    struct interpreter {
        char broiled;
    }

    alias interpreter PerlInterpreter;
    alias void function(PerlInterpreter*) XSINIT_t;
    ubyte PL_exit_flags;

    /* Perl Prototypes */
    int Perl_call_argv(PerlInterpreter*, char *subname, int flags, char **argv);
    int perl_destruct(PerlInterpreter*);
    int perl_parse(PerlInterpreter*, XSINIT_t xsinit, int argc, char** argv, char** env);
    int perl_run(PerlInterpreter*);
    void perl_construct(PerlInterpreter*);
    void perl_free(PerlInterpreter*);
    void Perl_sys_init3(int* argc, char*** argv, char*** env);
    void Perl_sys_term();
    PerlInterpreter* perl_alloc();
 
    /* Unix System Environment */
    extern __gshared char** environ;
}


/* Copy a char[][] to a char** */
private void buildStringA(char[][] a, char** ap)
{
    foreach (s; a)
        *ap++ = (s ~ '\0').ptr;
    *ap = null;
}

/* Copy a char[] into a char* */
private void buildString(char[] a, char* ap)
{
    foreach (c; a)
        *ap++ = c;
    *ap = '\0';
}


/*** The Perl Interpreter Class ***/
class perlInterpreter
{
    int argc;
    char** argv;
    PerlInterpreter* my_perl;
    int parsestatus = 1;
    int exitstatus;

    this(ref char[][] args)
    {
        auto _argv = cast (char**)malloc ((char*).sizeof * (1 + args.length));
        buildStringA (args, _argv);
        argv = _argv;
        argc = cast (int)args.length;
    }

    this(int _argc, char** _argv)
    {
        argc = _argc;
        argv = _argv;
    }

    ~this()
    {
        if (argv)
            free (argv);
    }

    void Construct()
    {
        if (!my_perl)
        {
            Perl_sys_init3 (&argc, &argv, &environ);
            my_perl = perl_alloc();
            perl_construct (my_perl);
        }
    }
 
    void ExitFlags(uint flag)
    {
        PL_exit_flags |= flag;
    }
 
    void Parse()
    {
        if (my_perl)
            parsestatus = perl_parse (my_perl, null, argc, argv, null);
    }

    void Run()
    {
        if (my_perl && !parsestatus)
            exitstatus = perl_run (my_perl);
    }

    void CallSub(char[] subname, int flags)
    {
        auto sub = cast (char*)alloca ((char).sizeof * (1 + subname.length));
        buildString (subname, sub);

        if (my_perl && !parsestatus)
        {
            if (flags & G_DISCARD)
                Perl_call_argv (my_perl, sub, flags, argv);
            else
                exitstatus = Perl_call_argv (my_perl, sub, flags, argv);
        }
    }
 
    void Destruct()
    {
        if (my_perl)
        {
            perl_destruct (my_perl);
            perl_free (my_perl);
            Perl_sys_term();
        }
    }
}

And to build the library, simply use your D compiler of choice.

gdc -c -O2 -frelease Perl/Interpreter.d
ar rcs libperld.a Interpreter.o
ranlib libperld.a

Adding a Perl interpreter to your D program

And I’ll demonstrate how you can use the class in D.

import Perl.Interpreter;

int main(char[][] args)
{
    perlInterpreter myPerl = new perlInterpreter (args);
    myPerl.Construct();
    myPerl.ExitFlags(EXIT_DESTRUCT_END);
    myPerl.Parse();
    myPerl.Run();
    myPerl.Destruct();
    return myPerl.exitstatus;
}

And build it with the linker option ‘-lperl’ which requires libperl-dev to be installed on your system.

gdc -c -O2 -frelease dminiperl.d
gdc dminiperl.o -L. -lperld -lperl -o dminiperl

And after successful compilation, you’ll be able to use dminiperl just like Perl itself.

dminiperl myperlprog.pl

or

$ dminiperl
print "Pretty Good Perl \n";
print "10890 - 9801 is ", 10890 - 9801, "\n";

or

dminiperl -e 'printf("%x\n", 3735928559)'

Calling a Perl subroutine from your D program

To call individual Perl subroutines, Perl has four call_* functions to allow interface to. For this class though, I have only included call_argv() which takes a string argument.

import Perl.Interpreter;

void main(char[][] args)
{
    perlInterpreter myPerl = new perlInterpreter (args);
    myPerl.Construct();
    myPerl.ExitFlags(EXIT_DESTRUCT_END);
    myPerl.Parse();
    /* Skipping .Run() */
    myPerl.CallSub("showtime", G_DISCARD | G_NOARGS);
    myPerl.Destruct();
}

where showtime is a Perl subroutine that takes no arguments (that’s the G_NOARGS) and for which I’ll ignore the return value (that’s the G_DISCARD). Those flags, and others, are discussed in perlcall.

And to define the showtime subroutine in a file called showtime.pl:

print "I shan't be printed.";

sub showtime {
    print time,"\n";
}

And compile and run.

$ gdc -c -O2 -frelease showtime.d
$ gdc showtime.o -L. -lperld -lperl -o showtime
$ showtime showtime.pl
1277201943

yielding the number of seconds that elapsed between January 1, 1970 (the beginning of the Unix epoch), and the moment I began writing this sentence. :-)

Extending this D Class

For those who know perlembed well, this is just the icing of just what you can do. Sadly however, the more luxurious features, such as evaluating a Perl statement from your D program, require a little more complete D library, and a lot more time to write than I have to give in this particular essay.

If anyone wants to access any of the code / examples in this post from a source repository, or wish to collaborate in writing a complete Perl Library Interface for D. Just shout.

Moral?

You can sometimes write faster code in D, but you can always write code faster in Perl. Because you can use each from the other (with a little bit of work), combine them as you wish. :-)

Regards

Advertisements

About ibuclaw
An avant developer and support analyst.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: