The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Macro - Simple code templating mechnism

SYNOPSIS

  use Macro;

  macro { 'aif' <perl_codeblock:()> }
        { my ($condition) = @_;
          return "if (my \$it = ($condition)) "; }

  aif (func()) {
    print "$it is true\n";
  } else {
    print "$it is false\n";
  }

DESCRIPTION

Macro defines perl functions which transform perl source code. It allows you to specify a template of perl code and whenever source doce is encountered which matches that template is is substituted with the output of a function.

Writing macros is fundamentally different from writing functions, because while functions operate on data, macros operate on code. While a function is passed it's arguments a macro is passed the i<expressions> (code) which defines the arguments. This allows you to do really new things (program the programming language) but also really mess things up. It's a laser scalpal, do not point at eyes.

Using Macro

Every macro consists of two parts, a template and an expander. The template specifies what to transform and the expander specifies how to transform it.

The Template

The tempalte defines what piece of perl code should be transformed. It can consist of literal tokens, regexps, directives or builtin tokens.

literal tokens

A string enclosed in double or single quotes. matches itself.

regexps

A regular expression. Note that it is not nessecary to paren group anything, the entire match will always be returned, whether you like it or not.

directives

An instruction to the parser. all of the directives in Parse::RecDescent are available, but these are the most usefull:

<perl_codeblock>
<perl_variable>
<perl_quotelike>
builtin tokens

Certain unquoted and un <> bracketed words can appear in the template. These are really just Parse::RecDescent rules, but you don't need to know that if you don't want.

arg_list

A comma seperated list "things". "Things" could be better described as quoted and quote like strings:

regexps
"stuff like this"
'stuff like this as well'
s/i want/more/sex

(sorry, i couldn't help myself)

The expander function will be passed an ARRAY ref containing everything matched. Note that no processing is doen on the matches, so if "hello" was one of the args then '"hello"' (not the quotes) will be one of the elements of the ARRAY ref.

You can pass parameters to the arg_list token in order to specify what characters should be used for what. The first arg specifies the character to use to divide the args, the second sepcifies the opening character and the third specifies the closing character (if the third is not present it default's to the second paramter). Args are passed enclosed in '[' and ']' and comma seperated (this is just the rules Parse::RecDescent uses for passing args to rules).

This will give you arg_list's default behaviour:

  arg_list[",","(",")"]

If you're a curly kindof guy:

  arg_list["~","{","}"]

This will excpet an arg list to look like:

  { "foo" ~ "bar" ~ $x }

Just don't use '/' as a opening delimiter or closing delimiter as that will make it look like a regexp and the macro won't match (don't ask why).

Experimental

Opening and closing can be regexps and not just chars, however this is farily new and untested. Besides, you have to remove the quotemeta call which opens up a whole other can or worms.

function_name

a short hand for /[A-Za-z_][A-Za-z0-9_]*/

integer

short hand for /[-+]?\d+/

real

short hand for /-?\d+\.?\d*/

If you want to add your own builtin tokens you can append the rules (read Parse::RecDescent and see the definition of Macro::standard_rules to figure out how) to the global variable $Macro::standard_rules. The better thing would be to send them to me so i (and others, of course) can have them.

The Expander

The expander consists of regular perl code (it can be viewed as a sub minus the sub keyword) whose return value is perl code. The arguments to the generator code are the code pieces "captured" by the parameter directives in the template.

generator arguments

If our template is:

  'aif' <perl_codeblock:()>

And the perl code is

  aif (func()) { ....

Then the generator code's @_ var will look like:

  ( 'aif', '(func())' )

Cool Macros (or Why You Want To Use Macros Too)

These are mainly just ideas of mine...

Local/Inner functions

While this can be done with local *f = sub { }; this is yet another way to do it.

macro
  macro { 'my' 'sub' function_name <perl_codeblock> }
        { 'local *' . $_[2] . ' = sub {' . $_[3] . '};' }
use
  my sub func { 5 }; 
  func();
note

Since this uses local, any called functions will see the new value of the function, in other words, this is a dynamic and not lexical scoped function

class accessor (getter/setter) definer

As opposed to doing funky tricks with AUTOLOAD and, in so doing, hiding what's really going on just to save typing, this macro will save even more typing than using AUTOLOAD (unless you have a lot of attributes) and is, in my opinion, more expressive.

macro
  macro { 'accessor' function_name }
        { 'sub ' . $_[1] . ' {
             my $self = shift;
             if (@_) {
               $self->{' . $_[1] '} = $_[0]
             }
             return @_ ? $self : $self->{' . $_[1] .'};
         }'
       }
use
  accessor name;
  accessor age;
Anamorphic if

The idea for this is taken from Paul Graham's "On Lisp".

Whenever you have an if statement and the clauses need to be able to access the value returned by the condition, this macro will create a new variable ($it) which holds that value

macro
  macro { 'aif' <perl_codeblock:()> }
        { 'if (my $it = ' . $_[2] . ') ' }
use
  aif (func()) {
    print "the call to func returned a true value\n";
    print "in particular: $it\n";
  }
Temporary values

Whenever you need to mess with a value and when you're done you want to the old value to be put back. The if statement is necessary because we can't have lexical globs, perl isn't a pure dynmaically typed language, oh well.

macro
  macro { 'local-value' <perl_variable> <perl_codeblock> }
        { my ($var, $code) = @_[2,3]; 
          my $saved_var = sprintf("__%09d__",
                          int(rand() * 1000000000));
          if ($var =~ /^@/) {
            $saved_var = '@' . $saved_var;
          } elsif ($var =~ /^%/) {
            $saved_var = '%' . $saved_var;
          } else {
            $saved_var = '$' . $saved_var;
          }
          # notice that we don't backquote I<any> of these
          # vars
          return " { my $saved_var = $var;
                     $code;
                     $var = $saved_var} "
        }
use
  my $a = 5;
  print "a is $a\n";
  local-value $a {
     $a = 6;
     print "a is $a\n";
  }
  print "a is $a\n";
Continuations

See Continuations.pm (not that it exists yet...)

BUGS

Speed

It's horendously slow...

Suggestion: Instead of gnerating a grammar for every macro, you should generate a single grammar which can expand all macros, might help, who knows? However, what about macros which expand into macro definitions? agreed that we need to do some kind of optimizations, but how can we avoid calling Parse::RecDescent->new and still keep the flexibilty of macro which define macros? maybe this is someting we should compromies on?

Inclusion

Every macro used by a file has to been defined in that file, there is currently no way to include macro definitions from other files. Well, there is i just haven't documented how yet.

ISSUES

The real solution would be to be able to interact with the perl's evaluator. When perl sees a function (or keyword) which happens to have the macro attribute that function should be called immediatly and it's return value read in. (*cough* lisp *cough*)

Filter::Simple

In writing this code it would have been convient if Fitler::Simple would allow me to select the parts of source code i want to work on (code, regexp, quotes) while at the same time allowing me to conviently see the actual, unmodified original code.

So Filter::Simple was modified. A function Filter::Simple::show was added (it only exists while the transformation code is running) which reinserts whatever had been pulled out.

At the moment this function is inserted in Fitler::Simple's call space, should it be put in the caller's call space?

Parse::RecDescent

  • It it ocasionally useful to specify, vie the perl_codeblock directrive, what pair of chars you want to use as delimiters, you can do that now. Ceveat (sp?): if you want to extract '<>' delimited do

      <perl_codeblock:<>

    it's a dirty hack, but it works (and since all of my modifications to perl_codeblock were dirty hacks this didn't hurt too much)

  • Autogenerated actions are used a lot, so we needed a way to turn of the warnings. using the global $::AUTO_ACTION_NOWARN we can now silence these warnings.

AUTHOR

Edward Marco Baringer <e.baringer@studenti.to.it>

COPYRIGHT

Copyright (c) 2002, Edward Marco Baringer. All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the terms of the Perl Artistic License (see http://www.perl.com/perl/misc/Artistic.html)