Writing a macro in Perl

There are several ways to handle something similar to a C macro in Perl: a source filter, a subroutine, Template::Toolkit, or use features in your text editor.

Source Filters

If you gotta have a C / CPP style preprocessor macro, it is possible to write one in Perl (or, actually, any language) using a precompile source filter. You can write fairly simple to complex Perl classes that operate on the text of your source code and perform transformations on it before the code goes to the Perl compiler. You can even run your Perl code directly through a CPP preprocessor to get the exact type of macro expansions you get in C / CPP using Filter::CPP.

Damian Conway's Filter::Simple is part of the Perl core distribution. With Filter::Simple, you could easily write a simple module to perform the macro you are describing. An example:

package myopinion;
# save in your Perl's @INC path as "myopinion.pm"...
use Filter::Simple;
FILTER { 
    s/Hogs/Pigs/g; 
    s/Hawgs/Hogs/g;
    }
1; 

Then a Perl file:

use myopinion;
print join(' ',"Hogs", 'Hogs', qq/Hawgs/, q/Hogs/, "\n");
print "In my opinion, Hogs are Hogs\n\n";

Output:

Pigs Pigs Hogs Pigs 
In my opinion, Pigs are Pigs

If you rewrote the FILTER in to make the substitution for your desired macro, Filter::Simple should work fine. Filter::Simple can be restricted to parts of your code to make substations, such as the executable part but not the POD part; only in strings; only in code.

Source filters are not widely used in in my experience. I have mostly seen them with lame attempts to encrypt Perl source code or humorous Perl obfuscators. In other words, I know it can be done this way but I personally don't know enough about them to recommend them or say not to use them.

Subroutines

Sinan Ünür openex subroutine is a good way to accomplish this. I will only add that a common older idiom that you will see involves passing a reference to a typeglob like this:

sub opensesame {
   my $fn=shift;
   local *FH;
   return open(FH,$fn) ? *FH : undef;
}

$fh=opensesame('> /tmp/file');

Read perldata for why it is this way...

Template Toolkit

Template::Toolkit can be used to process Perl source code. For example, you could write a template along the lines of:

[% fw(fp, outfile) %] 

running that through Template::Toolkit can result in expansion and substitution to:

open my $FP, '>', $outfile or die "$outfile could not be opened for writing:$!"; 

Template::Toolkit is most often used to separate the messy HTML and other presentation code from the application code in web apps. Template::Toolkit is very actively developed and well documented. If your only use is a macro of the type you are suggesting, it may be overkill.

Text Editors

Chas. Owens has a method using Vim. I use BBEdit and could easily write a Text Factory to replace the skeleton of a open with the precise and evolving open that I want to use. Alternately, you can place a completion template in your "Resources" directory in the "Perl" folder. These completion skeletons are used when you press the series of keys you define. Almost any serious editor will have similar functionality.

With BBEdit, you can even use Perl code in your text replacement logic. I use Perl::Critic this way. You could use Template::Toolkit inside BBEdit to process the macros with some intelligence. It can be set up so the source code is not changed by the template until you output a version to test or compile; the editor is essentially acting as a preprocessor.

Two potential issues with using a text editor. First is it is a one way / one time transform. If you want to change what your "macro" does, you can't do it, since the previous text of you "macro" was already used. You have to manually change them. Second potential issue is that if you use a template form, you can't send the macro version of the source code to someone else because the preprocessing that is being done inside the editor.

Don't Do This!

If you type perl -h to get valid command switches, one option you may see is:

-P                run program through C preprocessor before compilation

Tempting! Yes, you can run your Perl code through the C preprocessor and expand C style macros and have #defines. Put down that gun; walk away; don't do it. There are many platform incompatibilities and language incompatibilities.

You get issues like this:

#!/usr/bin/perl -P
#define BIG small
print "BIG\n";
print qq(BIG\n);

Prints:

BIG
small

In Perl 5.12 the -P switch has been removed...

Conclusion

The most flexible solution here is just write a subroutine. All your code is visible in the subroutine, easily changed, and a shorter call. No real downside other than the readability of your code potentially.

Template::Toolkit is widely used. You can write complex replacements that act like macros or even more complex than C macros. If your need for macros is worth the learning curve, use Template::Toolkit.

For very simple cases, use the one way transforms in an editor.

If you really want C style macros, you can use Filter::CPP. This may have the same incompatibilities as the perl -P switch. I cannot recommend this; just learn the Perl way.

If you want to run Perl one liners and Perl regexs against your code before it compiles, use Filter::Simple.

And don't use the -P switch. You can't on newer versions of Perl anyway.


Perl 5 really doesn't have macros (there are source filters, but they are dangerous and ugly, so ugly even I won't link you to the documentation). A function may be the right choice, but you will find that it makes it harder for new people to read your code. A better option may be to use the autodie pragma (it is core as of Perl 5.10.1) and just cut out the or die part.

Another option, if you use Vim, is to use snipMate. You just type fw<tab>FP<tab>outfile<tab> and it produces

open my $FP, '>', $outfile
    or die "Couldn't open $outfile for writing: $!\n";

The snipMate text is

snippet fw
    open my $${1:filehandle}, ">", $${2:filename variable}
        or die "Couldn't open $$2 for writing: $!\n";

    ${3}

I believe other editors have similar capabilities, but I am a Vim user.


First, you should write that as:

open my $FP, '>', $outfile or die "Could not open '$outfile' for writing:$!";

including the reason why open failed.

If you want to encapsulate that, you can write:

use Carp;

sub openex {
    my ($mode, $filename) = @_; 
    open my $h, $mode, $filename
        or croak "Could not open '$filename': $!";
    return $h;
}

# later

my $FP = openex('>', $outfile);

Starting with Perl 5.10.1, autodie is in the core and I will second Chas. Owens' recommendation to use it.

Tags:

Perl

Macros