Perl Weekly Challenge 029: Brace expansion and calling a C function
Brace expansion
Write a script to demonstrate brace expansion. For example, the script would take the command line argumentPerl {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
You’ve probably heard about the glob function. It can expand wildcards like *
or ?
, so you e.g. can easily check what files correspond to p*.p?
. But glob can do one more thing for us: brace expansion.
Just calling glob
doesn’t give the expected output, though, as glob
treats whitespace specially: it first splits the input on whitespace into a list, and then expands each element of the list separately.
glob 'Perl {Daily,Weekly,Monthly,Yearly} Challenge'
returns
Perl
Daily
Weekly
Monthly
Yearly
Challenge
We need to first escape whitespace by backslashes.
use Syntax::Construct qw{ /r };
sub expand {
my ($string) = @_;
glob $string =~ s/(\s)/\\$1/gr
}
We now get the expected output, but the function still isn’t perfect: {a,,}
expands to "a", "", ""
, but in bash (which supports the brace expansion) the same expression returns just "a"
. In fact, we need to remove all the empty strings from the results:
grep length, glob $string =~ s/(\s)/\\$1/gr
Note that in bash, you can use {a,"",""}
which doesn’t exclude the empty strings, but the quotes aren’t part of the brace expansion, yet rather a way how to pass an empty string to the shell (in fact, quote removal happens after all other expansions in bash, and it should remove “all unquoted occurrences of the characters \
, '
, and "
that did not result from one of the expansions”—I’m not sure whether the double quotes here are or aren’t results of the brace expansion; shell is hard).
I tried to implement the brace expansion in Perl myself, but my solutions weren’t able to pass all the tests I prepared.
use Test::More tests => 5;
use Test::Deep;
cmp_deeply [expand('Perl {Daily,Weekly,Monthly,Yearly} Challenge')],
bag('Perl Daily Challenge',
'Perl Weekly Challenge',
'Perl Monthly Challenge',
'Perl Yearly Challenge'),
'Original example';
cmp_deeply [expand('{a,,}')],
bag(qw( a )),
'Empty results excluded';
cmp_deeply [expand('a{b,c}{d,e}f{g,h,i}')],
bag('abdfg', 'abdfh', 'abdfi',
'abefg', 'abefh', 'abefi',
'acdfg', 'acdfh', 'acdfi',
'acefg', 'acefh', 'acefi'),
'Multiple brackets';
cmp_deeply [expand('{{a,b}c,d}e')],
bag(qw( ace bce de )),
'Nested brackets';
cmp_deeply [expand('{{a,b}c,d}{g,}')],
bag(qw( acg ac bcg bc dg d )),
'Everything combined';
If you’re interested in a solution, check the Rosetta Code.
Calling a C function
Write a script to demonstrate calling a C function. It could be any user defined or standard C function.
In Perl, you can use XS to declare functions in C and make them callable from Perl. There’s also the Inline::C module which does lots of the work for you: just write the C code and call the functions from Perl.
I tried to call two functions, one of them returning a number and the other a string. To work with strings, you need to use some of the macros mentioned in perlguts—Introduction to the Perl API to prevent segmentation faults.
#!/usr/bin/perl
use warnings;
use strict;
use Inline 'C';
use Test::More tests => 2;
is factorial(20), 2432902008176640000;
is greet('world'), 'Hello world!';
__END__
__C__
long factorial (long n) {
long r = 1;
for (long i = 2; i <= n; i++) r *= i;
return r;
}
SV *greet (SV* name) {
return(newSVpvf("Hello %s!", SvPV(name, PL_na)));
}
SvPV
creates a scalar value from a C string value, newSVpvf
is similar, but includes formatting of the string (like sprintf).
Leave a comment