http://qs1969.pair.com?node_id=163932


in reply to A Macro System for Perl?

Hello

I believe this is a very interesting and important thing. Macros does give a language more expression power. Perl, in its deepest philosophy, is about giving the programmer the ability to express his/her ideas with the most succecent way, and macroes would do that.

Consider the Switch module by TheDamian. Here is a small snippet from the snippets section in the docs:

use Switch; switch ($val) { case 1 { print "number 1" } case "a" { print "string a" } case [1..10,42] { print "number in list" } case (@array) { print "number in list" } case /\w+/ { print "pattern" } case qr/\w+/ { print "pattern" } case (%hash) { print "entry in hash" } case (\%hash) { print "entry in hash" } case (\&sub) { print "arg to subroutine" } else { print "previous case not true" } }
This implementation of Switch added to the syntax of perl something that wasn't there and was unexpressable in perl. Now Switch was implemented using the low level Filter::Util::Call, and the code doesn't look pretty (but that's not important now as long as it can be done). The problem lies in the fact that you cannot mix two modules that extend the syntax. Imagine mixing Switch with Lingua::Romana::Perligata to get a Latin Switch :-). In Scheme (or Lisp), you can extend the syntax of the language without breaking compatibility with other syntactic modules.

In Scheme, the syntax recognizer/expander works before the code is passed to the actual compiler. The reader expands all macroes and passes them to the compiler that recognizes only ther core operators and functions (See the scheme revised report to see how let can be expressed using a lambda expression or vise versa and how letrec/let* can be expressed in terms of let and set!). Now that's easy to do for lisp, but not impossible to do in perl. To achieve this in perl, first you need a tokenizer that can accept arbitrary possible expressions without regurous checking (should accept "grep map test 'hahaha' and die;") and build the parse tree that is later modifies by the syntax expander. After all syntactical expressions are expanded, the code is passed to the actual perl compiler.

Now back to Switch. If we had a macro system, the implementation of switch would be simple straight forward:

syntax (switch (EXPR) CODE) { # eval the expr first to avoid # reevaluation at each case my $var = new_unique_symbol; code { { $var = EXPR; switch_aux($var) CODE } } } syntax (switch_aux (EXPR) { case CODE_t { CODE_c } CODE_e }) { # else is left as an excercise. code{ if(case(EXPR,CODE_t)){ CODE_c } else { switch_aux(EXPR){ CODE_e } # notice the recursion here } } } syntax (case (EXPR,INTEGER)){ code { (EXPR == INTEGER) } } syntax (case (EXPR,STRING)){ code { (EXPR eq STRING) } } syntax (case (EXPR,LIST)){ code { (grep EXPR, LIST) } } syntax (case (EXPR,REGEX)){ code { (EXPR =~ REGEX ) } } syntax (case (EXPR,HASH)){ code { (exists EXPR, HASH) } } syntax (case (EXPR,SUB)){ code { SUB(EXPR) } }
That was my slightly more than $0.02 worth of comments. Hope it helps,

Update: One more note: Parse::RecDescent would feel more at home if we have such syntax expansion so that instead of passind all the rules and actions as one string, they would be incorporated into perl directly. Example:

parser = new Parse::RecDescent (q{ expression: and_expr '||' expression | and_expr and_expr: not_expr '&&' and_expr | not_expr not_expr: '!' brack_expr | brack_expr brack_expr: '(' expression ')' | identifier identifier: /[a-z]+/i }); # becomes after dropping the quoting operator q{} parser = new Parse::RecDescent( expression: and_expr '||' expression | and_expr and_expr: not_expr '&&' and_expr | not_expr not_expr: '!' brack_expr | brack_expr brack_expr: '(' expression ')' | identifier identifier: /[a-z]+/i );