2011-10-29 25 views
3

這是一個很大的問題,請耐心等待。最後有一壺金。擴展MooseX時注入代碼的語法錯誤::聲明

對於主要是實驗性的原因,我試圖做一個MooseX :: Declare的自定義擴展,它會對特定的愛好項目做一些額外的魔術。例如,我想讓class關鍵字注入一些額外的東西,比如從List :: Util等導入有用的實用程序,打開各種額外的編譯指示(除strictwarnings之外),自動導入我的全局Config對象,以及等等。

因此,我寫了下面的測試,然後出發,看看我能否得到它的工作。令人驚訝的是,我能夠獲得99%的出路,但現在我遇到了一個我無法解決的問題。我的自定義class關鍵字在注入的代碼中死於語法錯誤。

#!/usr/bin/env perl 

use MyApp::Setup; 

class Foo { 
    use Test::More tests => 1; 

    has beer => (is => 'ro', default => 'delicious'); 
    method something { 
     is $self->beer, 'delicious'; 
    } 
} 


Foo->new->something; 

MyApp::Setup看起來像下面那樣。在未來,會做一些更多的東西,但現在它只是我的MX調用import :: d子類:

package MyApp::Setup; 

use strict; 
use warnings; 

use MyApp::MooseX::Declare; 

sub import { 
    goto &MyApp::MooseX::Declare::import; 
} 

1; 

而該類看起來像這樣:

package MyApp::MooseX::Declare; 

use Moose; 

use MyApp::MooseX::Declare::Syntax::Keyword::Class; 
use MyApp::MooseX::Declare::Syntax::Keyword::Role; 
use MyApp::MooseX::Declare::Syntax::Keyword::Namespace; 

extends 'MooseX::Declare'; 

sub import { 
    my ($class, %args) = @_; 

    my $caller = caller; 

    for my $keyword (__PACKAGE__->keywords) { 
     warn sprintf 'setting up keyword %s', $keyword->identifier; 
     $keyword->setup_for($caller, %args, provided_by => __PACKAGE__); 
    } 
} 

sub keywords { 
    # override the 'class' keyword with our own 
    return 
     (MyApp::MooseX::Declare::Syntax::Keyword::Class->new(identifier => 'class'), 
     MyApp::MooseX::Declare::Syntax::Keyword::Role->new(identifier => 'role'), 
     MyApp::MooseX::Declare::Syntax::Keyword::Namespace->new(identifier => 'namespace')); 
} 

1; 

我成立三個關鍵字類別只包括一個替代MX::D::Syntax::NamespaceHandling的額外角色。

package MyApp::MooseX::Declare::Syntax::Keyword::Class; 

use Moose; 

extends 'MooseX::Declare::Syntax::Keyword::Class'; 
with 'MyApp::MooseX::Declare::Syntax::NamespaceHandling'; 

1; 

(另外兩個是相同的。)

在實際MX :: d,所述NamespaceHandling東西由成稱爲MooseSetup單獨的作用,它本身就是構成到關鍵字類。在一個地方做這一切似乎工作;不過,我不知道結構上的輕微偏差是否是我的問題的根源。有一次我有我自己的MooseSetup版本,但是這導致了我無法弄清楚的組合衝突。

最後,肉和土豆是我版本的NamespaceHandling,它覆蓋parse方法。它的大部分只是從原件複製和粘貼。

package MyApp::MooseX::Declare::Syntax::NamespaceHandling; 

use Moose::Role; 
use Carp 'croak'; 
use Moose::Util 'does_role'; 
use MooseX::Declare::Util 'outer_stack_peek'; 


with 'MooseX::Declare::Syntax::NamespaceHandling'; 

# this is where the meat is! 

sub parse { 
    my ($self, $ctx) = @_; 

    # keyword comes first 
    $ctx->skip_declarator; 

    # read the name and unwrap the options 
    $self->parse_specification($ctx); 

    my $name = $ctx->namespace; 

    my ($package, $anon); 

    # we have a name in the declaration, which will be used as package name 
    if (defined $name) { 
     $package = $name; 

     # there is an outer namespace stack item, meaning we namespace below 
     # it, if the name starts with :: 
     if (my $outer = outer_stack_peek $ctx->caller_file) { 
      $package = $outer . $package 
       if $name =~ /^::/; 
     } 
    } 

    # no name, no options, no block. Probably { class => 'foo' } 
    elsif (not(keys %{ $ctx->options }) and $ctx->peek_next_char ne '{') { 
     return; 
    } 

    # we have options and/or a block, but not name 
    else { 
     $anon = $self->make_anon_metaclass 
      or croak sprintf 'Unable to create an anonymized %s namespace', $self->identifier; 
     $package = $anon->name; 
    } 

    warn "setting up package [$package]"; 

    # namespace and mx:d initialisations 
    $ctx->add_preamble_code_parts(
     "package ${package}", 
     sprintf(
      "use %s %s => '%s', file => __FILE__, stack => [ %s ]", 
      $ctx->provided_by, 
      outer_package => $package, 
      $self->generate_inline_stack($ctx), 
     ), 
    ); 

    # handle imports and setup here (TODO) 


    # allow consumer to provide specialisations 
    $self->add_namespace_customizations($ctx, $package); 

    # make options a separate step 
    $self->add_optional_customizations($ctx, $package); 

    # finish off preamble with a namespace cleanup 
    # we'll use namespace::sweep instead 

    #$ctx->add_preamble_code_parts(
    # $ctx->options->{is}->{dirty} 
    #  ? 'use namespace::clean -except => [qw(meta)]' 
    #  : 'use namespace::autoclean' 
    #); 

    # clean up our stack afterwards, if there was a name 
    $ctx->add_cleanup_code_parts(
     ['BEGIN', 
      'MooseX::Declare::Util::outer_stack_pop __FILE__', 
     ], 
    ); 

    # actual code injection 
    $ctx->inject_code_parts(
     missing_block_handler => sub { $self->handle_missing_block(@_) }, 
    ); 

    # a last chance to change things 
    $self->handle_post_parsing($ctx, $package, defined($name) ? $name : $anon); 
} 


1; 

當我運行測試,一切似乎去偉大的 - 我得到指示正確的方法被調用的警告消息和包「富」正在建立。然後,它與死亡:在t/default.t線5

語法錯誤,接近「{包富」

所以它看起來像東西是正確的之前或之後注射一些代碼package聲明導致語法錯誤,但我無法弄清楚什麼。我嘗試過隨機播放parse子版中的各種項目(我實際上並不知道他們都在做什麼),但似乎無法消除甚至改變錯誤。當然,我沒有辦法(我知道)實際檢查生成的代碼,這可能會產生線索。

感謝您的幫助。

有些更新:內MooseX四處尋找後::聲明::情況下,我加入了一些print報表,看看到底發生了什麼正在通過調用注入inject_code_parts。這是實際的代碼獲取生成(整理):

package Foo; 

use MyApp::MooseX::Declare outer_package => 'Foo', file => __FILE__, stack => [ 
    MooseX::Declare::StackItem->new(q(identifier), q(class), q(handler), 
    q(MyApp::MooseX::Declare::Syntax::Keyword::Class), q(is_dirty), q(0), 
    q(is_parameterized), q(0), q(namespace), q(Foo)) ];; 

BEGIN { Devel::Declare::Context::Simple->inject_scope('BEGIN { 
    MooseX::Declare::Util::outer_stack_pop __FILE__ }') }; ; 

我不能說我知道什麼都在支持(特別是outer_stack_pop的事情),但是這一切看起來語法OK給我。我仍然認爲在引發語法錯誤的所有這些之前,注入代碼是有道理的。

回答

1

那麼,這是一個調試會議的地獄,但我終於找到了問題,並得出它的想法。在破解了MooseX::Declare::ContextDevel::Declare::Context::Simple(前代表)之後,我能夠追蹤流程,並通過大量傾銷到STDOUT,我意識到MooseSetup.pm中的一些額外處理程序,我認爲這些處理程序已正確組成我的關鍵字類,實際上並不存在。因此注入的代碼沒有附加適當的陰影/清理內容。

無論如何,我現在有什麼似乎是完全正常工作的定製MooseX :: Declare!我真的很激動這一點 - 這意味着我可以輸入

use MyApp::Setup; 

class MyApp::Foo { ... } 

和一個class語句設置專用樣板的全亂了。拉德。

+0

請將此內容添加到博客,並將它添加到http://www.iinteractive.com/moose/articles.html。 – daxim

+0

我打算在本週晚些時候寫一篇文章給blogs.perl.org。 – friedo