Синтаксическая ошибка во введенном коде при расширении MooseX::Declare

#perl #parsing #moose

#perl #синтаксический анализ #moose

Вопрос:

Это большая ошибка, поэтому, пожалуйста, потерпите меня. В конце есть горшок с золотом.

В основном по экспериментальным причинам я пытаюсь создать пользовательское расширение MooseX::Declare, которое выполняет дополнительную магию, полезную для конкретного хобби-проекта. Например, я хочу, чтобы class ключевое слово добавляло немного дополнительных вещей, таких как импорт полезных утилит из List::Util и т.п., Включение различных дополнительных прагм (помимо strict и warnings ), автоматический импорт моего глобального объекта конфигурации и так далее.

Итак, я написал следующий тест и отправился посмотреть, смогу ли я заставить его работать. Удивительно, но я смог пройти 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 выглядит следующим образом. В будущем он будет делать еще кое-что, но сейчас он просто вызывает import мой подкласс MX :: D:

 package MyApp::Setup;

use strict;
use warnings;

use MyApp::MooseX::Declare;

sub import { 
    goto amp;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 материал для обработки пространства имен состоит из отдельной роли, называемой MooseSetup , которая сама состоит из ключевого слова class . Кажется, что все это в одном месте работает; Я не знаю, является ли небольшое отклонение в структуре источником моей проблемы. В какой-то момент у меня была своя версия MooseSetup, но это привело к конфликтам композиции, которые я не мог понять.

Наконец, мясо и картофель — это моя версия обработки пространства имен, которая переопределяет 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;
  

Когда я запускаю тест, кажется, все идет отлично — я получаю предупреждающие сообщения, указывающие на то, что вызываются правильные методы и что пакет «Foo» настраивается. Затем он умирает с:

синтаксическая ошибка при t /по умолчанию.t строка 5, рядом с «{package Foo»

Похоже, что что-то вводит какой-то код прямо перед или после package объявления, что вызывает синтаксическую ошибку, но я не могу понять, что. Я пробовал случайным образом играть с различными элементами в parse подразделе (на самом деле я не знаю, что они все делают на данный момент), но, похоже, я не могу устранить или даже изменить ошибку. И, конечно, нет способа (насколько я знаю) фактически проверить сгенерированный код, который мог бы дать подсказку.

Спасибо за вашу помощь.

Некоторые обновления: осмотревшись внутри MooseX ::Declare::Context, я добавил несколько 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 вещь), но все это выглядит синтаксически нормально для меня. Я все еще думаю, что что-то вводит код перед всем этим, что вызывает синтаксическую ошибку.

Ответ №1:

Что ж, это был адский сеанс отладки, но я, наконец, отследил проблему и разобрался с ней. После взлома откройте оба MooseX::Declare::Context и Devel::Declare::Context::Simple (на которые делегируются бывшие) Я смог отследить поток и через обильный сброс в стандартный вывод я понял, что некоторые из дополнительных обработчиков из MooseSetup.pm , которые, как мне казалось, я правильно включил в свои классы ключевых слов, на самом деле там не было. Таким образом, в результирующем вводимом коде не было надлежащего содержимого для теней / очистки.

В любом случае, теперь у меня есть то, что кажется полностью работающим настроенным MooseX::Declare! Я действительно в восторге от этого — это означает, что я могу печатать

 use MyApp::Setup; 

class MyApp::Foo { ... }
  

и этот один class оператор создает целую путаницу шаблонов для конкретного приложения. Рад.

Комментарии:

1. Пожалуйста, напишите об этом в блоге и включите его iinteractive.com/moose/articles.html .

2. Я планирую написать его для blogs.perl.org когда у меня будет немного времени позже на этой неделе.