Как мне создать что-то вроде отрицательного класса символов со строкой вместо символов? - PullRequest
4 голосов
/ 17 марта 2010

Я пытаюсь написать токенизатор для Усы на Perl. Я легко справлюсь с большинством токенов следующим образом:

#!/usr/bin/perl

use strict;
use warnings;

my $comment  = qr/ \G \{\{ !  (?<comment>  .+? ) }}              /xs; 
my $variable = qr/ \G \{\{    (?<variable> .+? ) }}              /xs; 
my $text     = qr/ \G         (?<text>     .+? ) (?= \{\{ | \z ) /xs; 
my $tokens   = qr/ $comment | $variable | $text /x;

my $s = do { local $/; <DATA> };

while ($s =~ /$tokens/g) {
    my ($type)    = keys %+;
    (my $contents = $+{$type}) =~ s/\n/\\n/;

    print "type [$type] contents [$contents]\n";
}

__DATA__
{{!this is a comment}}
Hi {{name}}, I like {{thing}}.

Но у меня проблемы с директивой Set Delimiters:

#!/usr/bin/perl

use strict;
use warnings;

my $delimiters = qr/ \G \{\{    (?<start> .+? ) = [ ] = (?<end> .+?) }} /xs; 
my $comment    = qr/ \G \{\{ !  (?<comment>  .+? ) }}                   /xs; 
my $variable   = qr/ \G \{\{    (?<variable> .+? ) }}                   /xs; 
my $text       = qr/ \G         (?<text>     .+? ) (?= \{\{ | \z )      /xs; 
my $tokens     = qr/ $comment | $delimiters | $variable | $text /x;

my $s = do { local $/; <DATA> };

while ($s =~ /$tokens/g) {
    for my $type (keys %+) {
        (my $contents = $+{$type}) =~ s/\n/\\n/;

        print "type [$type] contents [$contents]\n";
    }
}

__DATA__
{{!this is a comment}}
Hi {{name}}, I like {{thing}}.
{{(= =)}}

Если я поменяю его на

my $delimiters = qr/ \G \{\{ (?<start> [^{]+? ) = [ ] = (?<end> .+?) }} /xs;

Работает нормально, но смысл директивы Set Delimiters состоит в том, чтобы изменить разделители, поэтому код будет выглядеть как

my $variable = qr/ \G $start (?<variable> .+? ) $end /xs;

И совершенно справедливо сказать {{{== ==}}} (т.е. изменить разделители на {= и =}). То, что я хочу, но, возможно, не то, что мне нужно, это способность сказать что-то вроде (?:not starting string)+?. Я полагаю, что мне просто придется отказаться от чистоты и добавить код в регулярное выражение, чтобы заставить его соответствовать только тому, что я хочу. Я пытаюсь избежать этого по четырем причинам:

  1. Я не думаю, что это очень чисто.
  2. Помечено как экспериментальное.
  3. Я не очень знаком с ним (думаю, что доходит до (?{CODE}) и возвращает специальные значения.
  4. Я надеюсь, что кто-то знает какую-то другую экзотическую функцию, с которой я не знаком, которая лучше подходит к ситуации (например, (?(condition)yes-pattern|no-pattern)).

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

Ответы [ 2 ]

3 голосов
/ 18 марта 2010

Используйте отрицательное прогнозное утверждение. Примерно так:

my $variable = qr/ \G $start (?<variable> (.(?!$end))+ ) $end /xs;
2 голосов
/ 18 марта 2010

Для тех, кому интересно, ниже приведен полный токенизатор для Усов, написанный в стиле Perl 5.10. Теперь мне просто нужно написать синтаксический анализатор и средство визуализации.

#!/usr/bin/perl

use 5.010_000;
use strict;
use warnings;

sub gen_tokenizer {
    my ($s, $e) = @_;
    my ($start, $end) = map { quotemeta } $s, $e;

    my $unescaped = "$s $e" eq "{{ }}" ?
        qr/ \G \{{3}    (?<unescaped> .+?)  }{3} /xs :
        qr{ \G $start & (?<unescaped> .+? ) $end }xs;

    return qr{
        $unescaped                                                 |
        \G $start (?:
            !  (?<comment>    .+? )                                |
            >  (?<partial>    .+? )                                |
            \# (?<enum_start> .+? )                                |
            /  (?<enum_stop>  .+? )                                |
            (?<start> (?: . (?! $end ) )+? ) = [ ] = (?<end> .+? ) |
            (?<variable>      .+? )
        ) $end                                                     |
        (?<text> .+? ) (?= $start | \z )
    }xs; 
}

my $template  = do { local $/; <DATA> };
my $tokenizer = gen_tokenizer "{{", "}}";

while ($template =~ /$tokenizer/g) {
    my @types = keys %+;

    if (@types == 1) {
        my  $type     = $types[0];
        (my $contents = $+{$type}) =~ s/\n/\\n/g;

        say "$type: [$contents]";
    } else {
        $tokenizer = gen_tokenizer $+{start}, $+{end};

        say "set_delim: [$+{start} $+{end}]";
    }
}

__DATA__
{{!this is a comment}}
{{{html header}}}
Hi {{name}}, I like {{thing}}.
{{(= =)}}
(#optional)
This will only print if optional is set
(/optional)
(&html footer)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...