2017-03-13 12 views
10

Próbuję wykonać overload constants in regular expressions. Tu jest mój pakiet Tagger:Dlaczego moje przeciążenie :: constant sub nie jest wyzwalane, gdy używam zmiennej łańcuchowej?

package Tagger; 
use overload; 

sub import { overload::constant 'qr' => \&convert } 

sub convert { 
    my $re = shift; 
    $re =~ s/\\nom/((?:[A-Z]{1}[a-z]+\\s*){2,3}(\\((\\w|\\s)+\\)+?)*)/xg; 
    return $re; 
} 

1; 

tutaj jest podprogram w którym chciałbym wywołać przeciążenie:

sub ChopPattern { 
    my $string= shift; 
    my $pattern = shift; 

    if($string =~ m/$pattern/) { 
     $string =~ s/$&/ /g; 
     return ($string, $&); 
    } else { 
     return ($string, ''); 
    } 
} 

Oto moja próba:

$test = "foo bar Max Fast bar foo"; 
($test, $name) = ChopPattern($test, '\nom'); 
say $test; 
say $name; 

Gdybym przewodowego wzorzec testowy, \nom, w meczu podprogramu:

sub ChopPattern { 
    my $string= shift; 
    my $pattern = shift; 

    if($string =~ m/\nom/) { 
     $string =~ s/$&/ /g; 
     return ($string, $&); 
    } else { 
     return ($string, ''); 
    } 
} 

test daje poprawną odpowiedź:

foo bar bar foo 
Max Fast 

Ale jeśli mogę użyć $pattern w meczu jak powyżej wydajność testu:

foo bar Max Fast bar foo 
<null line> 

Czy istnieje powód, dla którego \nom wyzwala Tagger, ale zmienna równa się \nom nie powoduje?

Oto szczegóły dotyczące wersji Perl wykorzystywane:

This is perl 5, version 16, subversion 3 (v5.16.3) built for MSWin32-x64-multi-thread (with 1 registered patch, see perl -V for more detail) 

Copyright 1987-2012, Larry Wall 

Binary build 1604 [298023] provided by ActiveState http://www.ActiveState.com 
Built Apr 14 2014 15:29:45 
+0

Warto kilka przegranych, ponieważ nigdy nie przyszło mi do głowy, że można przeciążać silnik regex w ten sposób. – Sobrique

+2

@Sobrique: Jest to możliwe, ale nie jest to bardzo przyjemne. Akcja na odległość i tak dalej. – Borodin

+0

Nie widział tego również wcześniej. Najpierw pomyślałem, że źle zrozumieli, co robi "przeciążenie". Ale jest to całkiem logiczne. Po prostu nie widzę dla niego przypadku użycia. To dziwaczne. – simbabque

Odpowiedz

4

programowania Perl mówi, że overload::constant prace na stałe.

Wszelkie procedury obsługi podane dla liczby całkowitej i zmiennoprzecinkowej będą wywoływane za każdym razem, gdy tokenerator Perla napotka stałą liczbę.

Po wywołaniu m/$pattern/ nie jest to stała. To zmienna.

($test, $name) = ChopPattern($test, '\nom'); 

Teraz tam '\nom' jest stała, ale jest to ciąg znaków. Zmień to w qr//, a będziesz miał wyrażenie regularne, które zawiera stałą.

($test, my $name) = ChopPattern($test, qr'\nom'); 

Wzór meczu w ChopPattern może pozostać taka sama:

if($string =~ m/$pattern/) { ... } 

Bo teraz jest stałym elementem w wyrażeniu regularnym, Perl może zadzwonić do przeciążenia convert i wykonać regex.


Zobaczmy to w akcji. Pamiętaj, że Perl robi to przeciążanie podstawienia podczas kompilacji, kiedy analizuje kod źródłowy.

Rozważmy następujący przykład:

BEGIN { 
    overload::constant 'qr' => sub { 
     my $re = shift; 
     $re =~ s/\\nom/foobar/; 
     return $re; 
    }; 
} 

sub match { 
    my ($t, $p) = @_; 
    $t =~ m/$p/; 
} 
match('some text', '\nom'); 

to nie jest ważne, co kod robi. Kiedy go deparse, otrzymujemy następujący wynik:

$ perl -MO=Deparse scratch.pl 
sub BEGIN { 
    use warnings; 
    use strict; 
    use feature 'say'; 
    overload::constant('qr', sub { 
     my $re = shift(); 
     $re =~ s/\\nom/foobar/; 
     return $re; 
    } 
    ); 
} 
sub match { 
    use warnings; 
    use strict; 
    use feature 'say'; 
    BEGIN { 
     $^H{'qr'} = 'CODE(0x147a048)'; 
    } 
    my($t, $p) = @_; 
    $t =~ /$p/; 
} 
use warnings; 
use strict; 
use feature 'say'; 
BEGIN { 
    $^H{'qr'} = 'CODE(0x147a048)'; 
} 
match 'some text', '\\nom';      # <-- here 

Widzimy, że program obsługi został zainstalowany, ale w ostatniej linii w wywołaniu funkcji, istnieje ciąg '\\nom'.

Teraz, jeśli używamy cytowanego wyrażenia qr//, zamiast ciągów, rzeczy się zmieniają.

BEGIN { 
    overload::constant 'qr' => sub { 
     my $re = shift; 
     $re =~ s/\\nom/foobar/; 
     return $re; 
    }; 
} 

sub match { 
    my ($t, $p) = @_; 
    $t =~ m/$p/; 
} 
match('some text', qr/\nom/); 

Teraz program deparowany zawiera nagle foobar. Zmodyfikowano wyrażenie regularne.

$ perl -MO=Deparse scratch2.pl 
sub BEGIN { 
    use warnings; 
    use strict; 
    use feature 'say'; 
    overload::constant('qr', sub { 
     my $re = shift(); 
     $re =~ s/\\nom/foobar/; 
     return $re; 
    } 
    ); 
} 
sub match { 
    use warnings; 
    use strict; 
    use feature 'say'; 
    BEGIN { 
     $^H{'qr'} = 'CODE(0x1e81048)'; 
    } 
    my($t, $p) = @_; 
    $t =~ /$p/; 
} 
use warnings; 
use strict; 
use feature 'say'; 
BEGIN { 
    $^H{'qr'} = 'CODE(0x1e81048)'; 
} 
match 'some text', qr/foobar/;      # <-- here 

Zrobił to, zanim kod został uruchomiony.

Jeśli uruchomimy oba programy z -MO=Concise, aby zobaczyć, co interpreter będzie działał po czasie kompilacji, otrzymamy kolejny dowód, że te rzeczy działają tylko na rzeczywistych stałych w kodzie źródłowym i nie mogą działać dynamicznie.

$ perl -MO=Concise scratch.pl 
8 <@> leave[1 ref] vKP/REFC ->(end) 
1  <0> enter ->2 
2  <;> nextstate(main 2529 scratch.pl:5950) v:%,R,*,&,{,x*,x&,x$,$,469762048 ->3 
7  <1> entersub[t1] vKS/TARG,2 ->8 
-  <1> ex-list K ->7 
3   <0> pushmark s ->4 
4   <$> const(PV "some text") sM ->5  # <-- here 
5   <$> const(PV "\\nom") sM ->6 
-   <1> ex-rv2cv sK/2 ->- 
6    <$> gv(*match) s ->7 

I z qr//:

$ perl -MO=Concise scratch2.pl 
8 <@> leave[1 ref] vKP/REFC ->(end) 
1  <0> enter ->2 
2  <;> nextstate(main 2529 scratch2.pl:5950) v:%,R,*,&,{,x*,x&,x$,$,469762048 ->3 
7  <1> entersub[t1] vKS/TARG,2 ->8 
-  <1> ex-list K ->7 
3   <0> pushmark s ->4 
4   <$> const(PV "some text") sM ->5  # <-- here 
5   </> qr(/"foobar"/) lM/RTIME ->6 
-   <1> ex-rv2cv sK/2 ->- 
6    <$> gv(*match) s ->7 
5

Czy istnieje jakiś powód, że \nom wyzwala Tagger ale zmienna równa \nom nie?

Ponieważ '\nom' jest ciągiem dosłowny, a nie stały fragment regex:

$ perl -Moverload -E'BEGIN { overload::constant qr => sub { say "@_" } } $foo =~ "bar"' 
$ perl -Moverload -E'BEGIN { overload::constant qr => sub { say "@_" } } $foo =~ /bar/' 
bar bar qq 

Co robisz jest to zły pomysł. Poniższy realizacja jest znacznie łatwiejsze do zrozumienia i nie zmienia regex semantykę wszędzie:

use strict; 
use warnings 'all'; 
use 5.010; 

sub chop_pattern { 
    my ($string, $pattern) = @_; 

    my %mapping = (
     '\nom' => qr/((?:[A-Z][a-z]+\s*){2,3}(?:\([\w\s]+\)+?)*)/ 
    ); 

    if (exists $mapping{$pattern}) { 
     my $matched = $string =~ s/$mapping{$pattern}/ /g; 
     return $string, $1 if $matched; 
    } 

    return $string, ''; 
} 

my ($string, $chopped) = chop_pattern('foo Bar Baz qux', '\nom'); 
say "<$string> <$chopped>"; 

wyjściowa:

<foo qux> <Bar Baz > 

Zgaduję poszedł z przeciążenia, ponieważ chcemy, aby obsługiwać więcej niż jeden " magiczny ciąg (np. \nom). Zrobiłem to za pomocą prostego skrótu, który mapuje ciągi do wyrażeń regularnych.