# -*- cperl -*- # $Source: /usr/cvsroot/ConveyPerl/lib/Macro.pod,v $ # $Id: Macro.pod,v 1.1 2002/02/14 11:03:24 marco Exp $ # COPYRIGHT (C) 2002 Edward Marco Baringer =head1 NAME Macro - Simple code templating mechnism =head1 SYNOPSIS use Macro; macro { 'aif' } { my ($condition) = @_; return "if (my \$it = ($condition)) "; } aif (func()) { print "$it is true\n"; } else { print "$it is false\n"; } =head1 DESCRIPTION C 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 I, macros operate on I. While a function is passed it's arguments a macro is passed the i (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. =head2 Using C 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. =head2 The Template The tempalte defines what piece of perl code should be transformed. It can consist of literal tokens, regexps, directives or builtin tokens. =over =item literal tokens A string enclosed in double or single quotes. matches itself. =item regexps A regular expression. Note that it is not nessecary to paren group anything, the entire match will I be returned, whether you like it or not. =item directives An instruction to the parser. all of the directives in Parse::RecDescent are available, but these are the most usefull: =over =item =item =item =back =item 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. =over =item arg_list A comma seperated list "things". "Things" could be better described as quoted and quote like strings: =over =item regexps =item "stuff like this" =item 'stuff like this as well' =item s/i want/more/sex (sorry, i couldn't help myself) =back The expander function will be passed an C ref containing everything matched. Note that I 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 C 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 C uses for passing args to rules). This will give you C'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). B 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. =item function_name a short hand for /[A-Za-z_][A-Za-z0-9_]*/ =back =item integer short hand for /[-+]?\d+/ =item real short hand for /-?\d+\.?\d*/ =back 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. =head2 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. =over =item generator arguments If our template is: 'aif' And the perl code is aif (func()) { .... Then the generator code's C<@_> var will look like: ( 'aif', '(func())' ) =back =head1 Cool Macros (or Why You Want To Use Macros Too) These are mainly just ideas of mine... =over =item Local/Inner functions While this can be done with C this is yet another way to do it. =over =item macro macro { 'my' 'sub' function_name } { 'local *' . $_[2] . ' = sub {' . $_[3] . '};' } =item use my sub func { 5 }; func(); =item 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 =back =item class accessor (getter/setter) definer As opposed to doing funky tricks with C and, in so doing, hiding what's really going on just to save typing, this macro will save even more typing than using C (unless you have a I of attributes) and is, in my opinion, more expressive. =over =item macro macro { 'accessor' function_name } { 'sub ' . $_[1] . ' { my $self = shift; if (@_) { $self->{' . $_[1] '} = $_[0] } return @_ ? $self : $self->{' . $_[1] .'}; }' } =item use accessor name; accessor age; =back =item 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 (C<$it>) which holds that value =over =item macro macro { 'aif' } { 'if (my $it = ' . $_[2] . ') ' } =item use aif (func()) { print "the call to func returned a true value\n"; print "in particular: $it\n"; } =back =item 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 I dynmaically typed language, oh well. =over =item macro macro { 'local-value' } { 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 of these # vars return " { my $saved_var = $var; $code; $var = $saved_var} " } =item use my $a = 5; print "a is $a\n"; local-value $a { $a = 6; print "a is $a\n"; } print "a is $a\n"; =back =item Continuations See Continuations.pm (not that it exists yet...) =back =head1 BUGS =over =item 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? =item 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. =back =head1 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. I<(*cough* lisp *cough*)> =head2 Filter::Simple In writing this code it would have been convient if C 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 C was modified. A function C 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 C's call space, should it be put in the caller's call space? =head2 Parse::RecDescent =over =item * It it ocasionally useful to specify, vie the C 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 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) =item * Autogenerated actions are used a lot, so we needed a way to turn of the warnings. using the global C<$::AUTO_ACTION_NOWARN> we can now silence these warnings. =back =head1 AUTHOR Edward Marco Baringer =head1 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) =cut