Подтвердить что ты не робот

Могу ли я написать DSL в Perl?

Мы используем Perl для автоматизации тестирования GUI. Это было очень успешно. Мы написали очень легкий язык DSL для тестирования графического интерфейса. DSL очень похож на объектную модель.

Например, у нас есть объект Application в корне. Каждая панель свойств в приложении представляет собой объект View. Каждая страница под страницей называется самим объектом страницы. Из Perl мы отправляем команды в приложение GUI, и графический интерфейс интерпретирует эту команду и прекрасно реагирует на эту команду. Чтобы отправить команду, мы делаем следующее:

socket_object->send_command("App.View2.Page2.Activate()")
socket_object->send_command("App.View1.Page3.OKBtn.Click()")

Это не очень читаемо. Вместо этого я хочу написать Perl DSL для App, View и Page. Предоставляет ли Perl некоторую структуру DSL, где я могу сделать следующее?

App.View2.Page2.Activate();
App.View1.Page2.Click();

Где приложение должно быть экземпляром класса Application. Я должен получить объект View2 во время выполнения.

Как использовать такие вещи?

4b9b3361

Ответ 1

Вы можете сделать почти что угодно в Perl. Но вам нужно сделать некоторые странные вещи, чтобы заставить Perl выполнять синтаксис, который просто не Perl.

  • Чтобы справиться с тем, что у вас есть, вам придется много продвинутых трюков, которые по определению не поддерживаются. Вы должны:

    • overload оператор конкатенации '.' (требуется блаженная ссылка)
    • превратить стриктуры или создать AUTOLOAD субтитры, чтобы разрешить эти голые слова - конечно, вы могли бы написать субтитры для всех слов, которые вы хотели использовать (или использовать barewords модуль).
    • возможно, создать несколько пакетов с несколькими AUTOLOAD
  • Другим способом является исходные фильтры, я могу, вероятно, подобрать нижний предел только для упоминания этой возможности. Поэтому я бы точно не рекомендовал этот подход для людей, которые просят помощи. Но это там. Исходные фильтры (и я сделал свою долю) - это лишь одна из тех областей, где вы можете думать, что слишком умны для своего же блага.

    Тем не менее, если вы заинтересованы в Perl как языке "хозяин" DSL, то исходные фильтры не являются точно пределами. Однако, ограничивая это тем, что вы показываете, что вы хотите сделать, Perl6:: Attributes, вероятно, сделает большую часть того, что вам понадобится сразу после полка. Это займет . и перевести их в "- > ", которые Perl поймет. Но вы все равно можете взглянуть на исходные фильтры, чтобы понять, что происходит за кулисами.

    Я также не хочу оставлять эту тему, не предполагая, что большая часть разочарования, которое вы могли бы создать собственный фильтр источника (который я советую НЕ делать), облегчается с помощью Damian Conway Фильтр:: Простой.

  • Самое простое - отказаться от '.' оператора и просто вместо этого ожидает Perl-выглядящий код.

    App->View2->Page2->Activate(); 
    App->View1->Page2->Click();
    

    App будет либо пакетом, либо суб. Либо определяемый в текущем пакете, либо импортированный, который возвращает объект, благословленный в пакет, с подтекстом View2 (возможно, AUTOLOAD), который возвращает либо имя пакета, либо ссылку, блаженную в пакет, который понимает Page2, а затем, наконец, возврат от этого понимает Activate или Click. (См. учебник OO, если вам нужно.)

Ответ 2

Я рекомендую вам отказаться от попыток сделать причудливые вещи "DSL" и просто написать классы Perl для обработки объектов, которыми вы хотите управлять. Я рекомендую вам изучить новую объектную систему Moose Perl для этого, хотя традиционный Perl OO будет в порядке. Проведите через документацию Perl для учебников OO; они великолепны.

Ответ 3

Вызов метода в perl5 использует -> not ., поэтому он будет выглядеть как App->View2->Page2->Activate() или $App->View2->Page2->Active(), если вы не сделаете что-то действительно интересное (например, исходный фильтр). Предполагая, что все в порядке, вы можете использовать обычный материал Perl OO.

Теперь, следующая часть того, что вам нужно, это создать методы во время выполнения. Это на самом деле довольно просто:

sub _new_view {
    my ($view, $view_num);

    # ...
    # ... (code to create $view object)
    # ...

    my $sym = "App::View$view_num";
    *$sym = sub { return $view }; # can also use Symbol package
}

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

Это даст вам синтаксис. Наличие ваших объектов для генерации строки для перехода на send_command не должно быть так сложно.

Кроме того, я не слишком хорошо знаком с этим, но вы можете проверить Moose. У этого могут быть более легкие способы выполнить это.

Ответ 4

Фильтр источника DSL

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

Итак, я удаляю некоторые из моих предупреждений об исходных фильтрах и решил показать вам как простой может быть написан. Опять же, то, что вы делаете, не так сложно, и моя "фильтрация" ниже довольно проста.

package RemoteAppScript;
use Filter::Simple;    # The basis of many a sane source filter
use Smart::Comments;   # treat yourself and install this if you don't have 
                       # it... or just comment it out.

# Simple test sub
sub send_command { 
    my $cmd = shift;
    print qq(Command "$cmd" sent.\n);
    return;
}

# The list of commands
my @script_list;

# The interface to Filter::Simple method of source filters.
FILTER { 
    # Save $_, because Filter::Simple doesn't like you reading more than once.
    my $mod = $_;

    # v-- Here a Smart::Comment.
    ### $mod

    # Allow for whole-line perl style comments in the script
    $mod =~ s/^\s*#.*$//m;

    # 1. Break the package up into commands by split
    # 2. Trim the strings, if needed
    # 3. lose the entries that are just blank strings.
    @script_list 
        = grep { length } 
          map  { s/^\s+|\s+$//g; $_ } 
          split /;/, $mod
        ;
    ### @script_list

    # Replace the whole script with a command to run the steps.
    $_ = __PACKAGE__ . '::run_script();';
    # PBP.
    return;
};

# Here is the sub that performs each action.
sub run_script { 
    ### @script_list
    foreach my $command ( @script_list ) {
        #send_command( $command );
        socket_object->send_command( $command );
    }
}

1;

Вам нужно сохранить это в RemoteAppScript.pm где-нибудь, где ваш perl может его найти. (попробуйте perl -MData::Dumper -e 'print Dumper( \@INC ), "\n"', если вам нужно знать где.)

Затем вы можете создать файл "perl", который имеет следующее:

use RemoteAppScript;
App.View2.Page2.Activate();
App.View1.Page2.Click();

Однако

Нет реальной причины, по которой вы не можете прочитать файл, содержащий команды сервера. Это вызовет вызов FILTER. У вас будет

App.View2.Page2.Activate();
App.View1.Page2.Click();

в вашем файле script, и ваш файл perl будет выглядеть примерно так:

#!/bin/perl -w 

my $script = do { 
    local $/;
    <ARGV>;
};

$script =~ s/^\s*#.*$//m;

foreach my $command ( 
    grep { length() } map  { s/^\s+|\s+$//g; $_ } split /;/, $script 
) { 
    socket_object->send_command( $command );
}

И назовите его так:

perl run_remote_script.pl remote_app_script.ras

Ответ 5

http://search.cpan.org/dist/Devel-Declare/ - это современная альтернатива исходным фильтрам, которая работает при интеграции непосредственно в Perl-парсер и заслуживает внимания.

Ответ 6

Альтернативой переопределению '.' или синтаксису -> может быть использование синтаксиса пакета (::), т.е. создание таких пакетов, как App:: View2 и App:: View2:: Page2, когда View2/Page 2 создается, добавив к пакету AUTOLOAD, который делегирует метод App:: View:: Page или App:: View, что-то вроде этого:

В вашем приложении /DSL.pm:

package App::DSL;
use strict; 
use warnings;
# use to avoid *{"App::View::$view::method"} = \&sub and friends
use Package::Stash;

sub new_view(%);
our %views;

# use App::DSL (View1 => {attr1 => 'foo', attr2 => 'bar'}); 
sub import {
    my $class = shift;
    my %new_views = @_ or die 'No view specified';

    foreach my $view (keys %new_views) {
            my $stash = Package::Stash->new("App::View::$view");
        # In our AUTOLOAD we create a closure over the right
        # App::View object and call the right method on it
        # for this example I just used _api_\L$method as the
        # internal method name (Activate => _api_activate)
        $stash->add_package_symbol('&AUTOLOAD' =>  
            sub {  
                our $AUTOLOAD;
                my ($method) = 
                   $AUTOLOAD =~ m{App::View::\Q$view\E::(.*)};
                my $api_method = "_api_\L$method";
                die "Invalid method $method on App::View::$view"
                   unless my $view_sub = App::View->can($api_method);
                my $view_obj = $views{$view}
                    or die "Invalid View $view";
                my $sub = sub {
                        $view_obj->$view_sub();
                };
                     # add the function to the package, so that AUTOLOAD
                     # won't need to be called for this method again
                $stash->add_package_symbol("\&$method" => $sub);
                goto $sub;
            });
        $views{$view} = bless $new_views{$view}, 'App::View';
    }
}

package App::View;

# API Method App::View::ViewName::Activate;
sub _api_activate {
    my $self = shift;
    # do something with $self here, which is the view 
    # object created by App::DSL
    warn $self->{attr1};
}

1;

и в script:

use strict;
use warnings;
# Create App::View::View1 and App::View::View2
use App::DSL (View1 => {attr1 => 'hello'}, View2 => {attr1 => 'bye'});
App::View::View1::Activate();
App::View::View2::Activate();
App::View::View1::Activate();