Macro - Simple code templating mechnism
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"; }
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.
Macro
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.
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 tempalte defines what piece of perl code should be transformed. It can consist of literal tokens, regexps, directives or builtin tokens.
A string enclosed in double or single quotes. matches itself.
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.
An instruction to the parser. all of the directives in Parse::RecDescent are available, but these are the most usefull:
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.
A comma seperated list "things". "Things" could be better described as quoted and quote like strings:
(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.
ARRAY
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).
Parse::RecDescent
This will give you arg_list's default behaviour:
arg_list
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.
a short hand for /[A-Za-z_][A-Za-z0-9_]*/
short hand for /[-+]?\d+/
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 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.
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())' )
These are mainly just ideas of mine...
While this can be done with local *f = sub { }; this is yet another way to do it.
local *f = sub { };
macro { 'my' 'sub' function_name <perl_codeblock> } { 'local *' . $_[2] . ' = sub {' . $_[3] . '};' }
my sub func { 5 }; func();
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
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.
AUTOLOAD
macro { 'accessor' function_name } { 'sub ' . $_[1] . ' { my $self = shift; if (@_) { $self->{' . $_[1] '} = $_[0] } return @_ ? $self : $self->{' . $_[1] .'}; }' }
accessor name; accessor age;
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
$it
macro { 'aif' <perl_codeblock:()> } { 'if (my $it = ' . $_[2] . ') ' }
aif (func()) { print "the call to func returned a true value\n"; print "in particular: $it\n"; }
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 { '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} " }
my $a = 5; print "a is $a\n"; local-value $a { $a = 6; print "a is $a\n"; } print "a is $a\n";
See Continuations.pm (not that it exists yet...)
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?
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.
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*)
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.
Fitler::Simple
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.
Filter::Simple
Filter::Simple::show
At the moment this function is inserted in Fitler::Simple's call space, should it be put in the caller's call space?
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
<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.
$::AUTO_ACTION_NOWARN
Edward Marco Baringer <e.baringer@studenti.to.it>
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)
To install Macro, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Macro
CPAN shell
perl -MCPAN -e shell install Macro
For more information on module installation, please visit the detailed CPAN module installation guide.