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 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

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

About E. Choroba

user-pic I blog about Perl.