Perl Weekly Challenge: Week 29

Although I've been back from India for more than a week, I missed the deadline for this weeks challenge too. The reason is task 2 which as we shall see was the most difficult one yet. But first an easy one...

Week 29 Challenge 1:

Write a script to demonstrate brace expansion. For example, script would take command line argument Perl {Daily,Weekly,Monthly,Yearly} Challenge and should expand it and print like below:

Perl Daily Challenge

Perl Weekly Challenge

Perl Monthly Challenge

Perl Yearly Challenge

Not much to say here. This is the Perl5 version:

sub expand {
    my ($string) = @_;
    $string =~ / \{(.+)\} /msx;

    return map {
        (my $expansion = $string) =~ s/\{(.+)\}/$_/msx;
        $expansion;
    } split /,\s*/, $1;
}

say for expand($ARGV[0] // q{});

(Full code on Github.)

I was going to write "...and this is Perl6" but the big news this week is that Perl6 is officially going to be renamed Raku so I should get used to it.

...and this is Raku:

sub expand(Str $string) {
    $string ~~ / \{(.+)\} /;

    return $0.split(/\,\s*/).map({
        my $word = $_;
        (my $expansion = $string) ~~ s/\{.+\}/$word/;
        $expansion;
    });
}

sub MAIN(Str $string) {
    .say for expand($string);
}

(Full code on Github.)

Week 29 Challenge 2:

Write a script to demonstrate calling a C function. It could be any user defined or standard C function.

In my opinion, a big reason Perl lost ground to newer languages like Python and Ruby is that using it as a "glue" language to interface with or extend applications was tedious and complicated. By doing this task I wanted to see if it was as bad as I remembered it and if Raku had improved the situation any.

I began by writing a simple shared library in C. This is the header file:

#ifndef _HELLO_H_
#define _HELLO_H_

void hello();

#endif

(Full code on Github.)

This is the source file:

#include <stdio.h>
#include "hello.h"

void hello() {
    puts("Hello world!");
}

(Full code on Github.)

As you can see, it just defines one function which prints out the standard greeting of computer science lore.

This is a basic Makefile for building the library:

CC?=cc
CFLAGS:=-O2 -Wall -Wextra -D_REENTRANT -fpic -I.
PROG=libhello.so

$(PROG): hello.o
    $(CC) -shared -Wl,-soname,$(PROG) $^ -o $@

hello.o: hello.c
    $(CC) $(CFLAGS) -c -o $@ $< 

hello.c: hello.h

clean:
    -rm *.o $(PROG)

.PHONY: clean

(Full code on Github.)

Note this has only been tested on Linux but it should work on any modern and sane Unix variant (so likely not HP/UX.) The Makefile will probably require a lot of modification for Windows.

Now to make a Perl module that uses this library you need to create an interface file in a domain specific language called XS for linking eXternal Subroutines. Somewhere I have a book called "Extending and Embedding Perl" 1 which explains XS in detail but I can't remember where I put it. So I had to rely on the documentation that came with Perl namely the perlxstut perldoc. That guided me to the h2xs program that also comes with Perl. It reduces a lot of the boilerplate involved in XS but at the end of the day (and I did spend nearly a whole day on this!) I still could not succesfully call my hello() C function from Perl.

Happily, there is another solution. SWIG is a tool for generating interface modules for many scripting languages including Perl.

To use SWIG you also need to learn a domain specific language but this one is a lot simpler than XS. Here's what my SWIG interface file looks like:

%module Hello

%{
    #include "hello.h"
%}

void hello();

%perlcode %{
    @EXPORT = qw( hello );
%}

(Full code on Github.)

The %perlcode part is copied verbatim into the produced Perl module.

When processed by SWIG, this file creates a Perl Module, Hello.pm and a c wrapper that should be compiled into a shared library to be called by Hello.pm. I've written a Makefile to automate the whole process.

CC?=cc
PERLCFLAGS:=$(shell perl -MConfig -e'print join q{ }, @Config{qw(ccflags optimize cccdlflags)};' )
PERLINC:=$(shell perl -MConfig -e'print "$$Config{archlib}/CORE";')
LIBHELLO:=../c
CFLAGS:=$(PERLCFLAGS) -I$(PERLINC) -I$(LIBHELLO)
LDFLAGS:=-L$(LIBHELLO) -lhello -Xlinker -rpath $(LIBHELLO)

Hello.so: Hello_wrap.o
    $(CC) -shared $^ $(LDFLAGS) -o $@

Hello_wrap.o: Hello_wrap.c
    $(CC) $(CFLAGS) -c -o $@ $< 

Hello_wrap.c: Hello.i
    swig -perl $<

clean:
    -rm Hello_wrap.c Hello.pm *.o Hello.so

.PHONY: clean

(Full code on Github.)

The PERLINC line threw me for a few minutes. Then I remembered if you have a $ in a shell command you have to double it so it doesn't get interpreted by Make.

In LDFLAGS I use -rpath. Don't do this in a "real" module because it hard codes where the library will search for its' dependencies which will cause trouble for anyone with a different directory layout than you. Instead use a proper build system like my favorite Module::Build, or Dist::Zilla etc. to install everything in the standard places Perl looks for them. I only used rpath here because I want to run the script from my git repository only.

Once we have completed all that building, we can run a simple script like this:

use lib qw( . );
use Hello;

hello();

(Full code on Github.)

use lib is another ad hoc workaround for my lack of proper installation.

For Raku we have to write a module like this:

use v6;
unit module Hello;

use NativeCall;

sub libhello is export {
    return '../c/libhello.so';
}

sub hello() is native(&libhello) is export {*};

(Full code on Github.)

...and that's it! Just as in Perl5...

use lib '.';
use Hello;

hello();

(Full code on Github.)

...prints "Hello world!". I'm really impressed and excited by how simple this is and hope this portents well for the return of Perl, I mean Raku, as the "glue of the Internet."

1Amazon link. As an Amazon Associate I earn from qualifying purchases.