2012-01-27 15 views
1

サブプロトタイプを使用して、mapやgrepのような独自のsubsを定義することができます。つまり、最初のcoderef引数は、通常の匿名サブコマンドより構文が短くなっています。たとえば、次のようになります。Perl:後続のCoderef引数の構文的砂糖?

sub thunked (&) { $_[0] } 

my $val = thunked { 2 * 4 }; 

最初の引数がcoderefなので、ここではうまくいきます。しかし後者の議論では、単純には適切に解析されません。

私はGTK2コードクリーナーを書くように設計されたwithサブを作った。 (それが架空のコードがありますので、テストされていない)、このように見えるためのものです:

use 5.012; 
use warnings; 

use Gtk2 '-init';  

sub with ($&) { 
    local $_ = $_[0]; 
    $_[1]->(); 
    $_; 
} 

for (Gtk2::Window->new('toplevel')) { 
    $_->set_title('Test Application'); 
    $_->add(with Gtk2::VBox->new { 
     my $box = $_; 
     $box->add(Gtk2::Button->new("Button $_")) for (1..4); 
    }); 
    $_->show_all; 
} 
Gtk2->main; 

withが素敵な構文を動作させるための最初の引数としてブロックを取る必要があるため、それは動作しません。それを取り除く方法はありますか?

答えて

6

モジュールDevel::Declareには、Perlの構文を比較的安全に拡張するためのツールが含まれています。

Devel :: Declareを使用すると、withトークンにフックが作成され、その単語に到達するとパーサが停止します。そこから、あなたはパーサーを制御でき、{シンボルに達するまで先読みすることができます。その時点で、作業する必要があるものがあるので、有効なPerlに書き換えて、パーサーに渡します。

ファイルWith.pm中:

package With; 
use warnings; 
use strict; 
use Devel::Declare; 

sub import { 
    my $caller = caller; 
    Devel::Declare->setup_for (
     $caller => {with => {const => \&parser}} 
    ); 
    no strict 'refs'; 
    *{$caller.'::with'} = sub ($&) { 
     $_[1]() for $_[0]; 
     $_[0] 
    } 
} 

our $prefix = ''; 
sub get {substr Devel::Declare::get_linestr, length $prefix} 
sub set {  Devel::Declare::set_linestr $prefix . $_[0]} 

sub parser { 
    local $prefix = substr get, 0, length($_[0]) + $_[1]; 
    my $with = strip_with(); 
    strip_space(); 
    set "scalar($with), sub " . get; 
} 

sub strip_space { 
    my $skip = Devel::Declare::toke_skipspace length $prefix; 
    set substr get, $skip; 
} 

sub strip_with { 
    strip_space; 
    my $with; 
    until (get =~ /^\{/) { 
     (my $line = get) =~ s/^([^{]+)//; 
     $with .= $1; 
     set $line; 
     strip_space; 
    } 
    $with =~ s/\s+/ /g; 
    $with 
} 

し、それを使用する:

 
window add: Box(Button(1) Button(2) Button(3) Button(4)) 

を完全に異なるアプローチをスキップするようになります:印刷し

use With; 

sub Window::add {say "window add: ", $_[1]->str} 
sub Window::new {bless [] => 'Window'} 
sub Box::new {bless [] => 'Box'} 
sub Box::add {push @{$_[0]}, @_[1..$#_]} 
sub Box::str {"Box(@{$_[0]})"} 
sub Button::new {"Button($_[1])"} 

with Window->new { 
    $_->add(with Box->new { 
     for my $num (1 .. 4) { 
      $_->add(Button->new($num)) 
     } 
    }) 
}; 

with完全にキーワードとコンストラクタサブルーチンを生成するためのルーチンを記述します。

BEGIN { 
    for my $name (qw(VBox)) { # and any others you want 
     no strict 'refs'; 
     *$name = sub (&@) { 
      use strict; 
      my $code = shift; 
      my $with = "Gtk2::$name"->new(@_); 
      $code->() for $with; 
      $with 
     } 
    } 
} 

、その後、あなたのコードは

for (Gtk2::Window->new('toplevel')) { 
    $_->set_title('Test Application'); 
    $_->add(VBox { 
     my $box = $_; 
     $box->add(Gtk2::Button->new("Button $_")) for (1..4); 
    }); 
    $_->show_all; 
} 
5

のようになります。あなたはそれに対処することができ一つの方法はかなり役に立たないキーワードを追加することです。

performが本当に subにちょうどsugarier代替である
sub perform(&) { $_[0] } 

with GTK2::VBox->new, perform { ... } 

もう一つの方法は、限り、あなたは、あなたがwith引数を解析してブロックを解析を開始する準備が完了したら伝えるためにいくつかの方法を持っているとして、あなたのwithを実装するためにDevel::DeclareフィルタやSyntax::Keyword:: pluginを書くことである - バランスの取れた括弧はどうなります(したがって、中括弧が開きますが、ハッシュが問題になります)。そして、あなたは

with (GTK2::VBox->new) { ... } 

のようなものをサポートし、フィルタは、それが動作するかどうか、実際にサブを作成していないため、と干渉しないという利点があり、

do { 
    local $_ = GTK2::VBox->new; 
    do { 
     ...; 
    }; 
    $_; 
} 

のようなものにそれを書き直してみましょうことができ@_returnなどがあります。 do -ageの2つのレイヤーは、適切な場所にEndOfScopeフックを取り付けることができると思われます。

明白な欠点は、それが厄介で、髪の毛がかっていて、ソースフィルタであることです(たとえ熟練していても)、それを使ってコードをデバッグできるようにするには解決しなければならない問題があります。すべて。

関連する問題