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{});
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);
}
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
This is the source file:
#include <stdio.h>
#include "hello.h"
void hello() {
puts("Hello world!");
}
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
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 );
%}
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
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();
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 {*};
...and that's it! Just as in Perl5...
use lib '.';
use Hello;
hello();
...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.