這是一個很大的問題,請耐心等待。最後有一壺金。擴展MooseX時注入代碼的語法錯誤::聲明
對於主要是實驗性的原因,我試圖做一個MooseX :: Declare的自定義擴展,它會對特定的愛好項目做一些額外的魔術。例如,我想讓class
關鍵字注入一些額外的東西,比如從List :: Util等導入有用的實用程序,打開各種額外的編譯指示(除strict
和warnings
之外),自動導入我的全局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給我。我仍然認爲在引發語法錯誤的所有這些之前,注入代碼是有道理的。
請將此內容添加到博客,並將它添加到http://www.iinteractive.com/moose/articles.html。 – daxim
我打算在本週晚些時候寫一篇文章給blogs.perl.org。 – friedo