Perl: синтаксический сахар для последних аргументов Coderef? - PullRequest
1 голос
/ 27 января 2012

Используя суб-прототипы, мы можем определить наши собственные суб-прототипы, которые выглядят как карта или grep.То есть первый аргумент coderef имеет более короткий синтаксис, чем обычный анонимный подпрограмма.Например:

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

my $val = thunked { 2 * 4 };

Отлично работает здесь, так как первый аргумент - это coderef.Для последних аргументов, однако, он просто не будет анализироваться должным образом.

Я сделал подпрограмму with, предназначенную для того, чтобы сделать запись кода GTK2 более чистой.Он должен выглядеть следующим образом (непроверенный, поскольку он является гипотетическим кодом):

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 необходимо принять блок в качестве первого аргумента для работы хорошего синтаксиса.Есть ли способ снять это?

Ответы [ 2 ]

6 голосов
/ 27 января 2012

Модуль 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
}

и использовать его:

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))
        }
    })
};

Какие отпечатки:

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

Совершенно другой подход заключается в том, чтобы вообще пропустить ключевое слово 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 голосов
/ 27 января 2012

Один из способов справиться с этим - добавить совершенно бесполезное ключевое слово:

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

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

где perform на самом деле просто более сахарная альтернатива sub.

Другой способ - написать фильтр Devel :: Declare или Syntax :: Keyword :: plugin для реализации with, если у вас есть какой-то способ Скажите, когда вы закончите анализ аргумента with и будете готовы начать синтаксический анализ блочных скобок (сработала бы открывающая фигурная скобка, но тогда хэши стали бы проблемой). Тогда вы могли бы поддержать что-то вроде

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

и пусть фильтр переписывает что-то вроде

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

, который, если он работает, имеет то преимущество, что фактически не создает подпрограмму и, следовательно, не мешает @_, return и некоторым другим вещам. Мне кажется, что два слоя do необходимы для того, чтобы установить крюк EndOfScope в нужном месте.

Очевидными недостатками этого является то, что это сложно, это сложно, и это фильтр исходного кода (даже если он ручной), что означает, что есть проблемы, которые вы должны решить, если вы хотите, чтобы любой код, использующий его, был отлаживаемым вообще .

...