2013-07-11 4 views
16

Ich versuche, eine einfache Grammatik mit einer Perl-Regex zu verschachteln (beachten Sie, dass dies nicht für den produktiven Einsatz gedacht ist, sondern nur eine schnelle Analyse für die Bereitstellung von Editor-Hinweisen/Vervollständigungen). Zum Beispiel würdeWie kann man auf Gruppen zugreifen, die von rekursiven Perl-Regexes erfasst wurden?

my $GRAMMAR = qr{(?(DEFINE) 
    (?<expr> \((?&expr) \) | (?&number) | (?&var) | (?&expr) (?&op) (?&expr)) 
    (?<number> \d++) 
    (?<var> [a-z]++) 
    (?<op> [-+*/]) 
)}x; 

Ich mag Lage sein, dies zu laufen als

$expr =~ /$GRAMMAR(?&expr)/; 

und dann Zugriff auf alle Variablennamen. Doch nach perlre,

anzumerken, dass capture Gruppen innerhalb der Rekursion abgestimmt sind nicht zugänglich, nachdem die Rekursion zurückkehrt, so dass die zusätzliche Schicht aus einfangenden Gruppen notwendig ist. Daher würde $ + {NAME_PAT} nicht definiert, obwohl $ + {NAME} wäre.

Also anscheinend ist das nicht möglich. Ich könnte versuchen, einen (?{ code }) Block zu verwenden, um Variablennamen in einem Hash zu speichern, aber dies berücksichtigt Backtracking nicht (d. H. Der Nebeneffekt der Zuweisung bleibt auch bestehen, wenn die Variable zurückverfolgt wird).

Gibt es eine Möglichkeit, alles zu erfassen, das von einer bestimmten benannten Erfassungsgruppe erfasst wurde, einschließlich rekursiver Übereinstimmungen? Oder muss ich die einzelnen Stücke manuell graben (und damit alle Muster kopieren)?

+7

Verwenden Sie [Parse :: RecDescent] (http://p3rl.org/Parse::RecDescent) oder [Marpa :: R2] (http://p3rl.org/Marpa::R2). – choroba

+0

Beobachtung: das ')' vor dem '} x;' hat keine Übereinstimmung in der Regex. –

+0

Hoppla, ich habe in der ersten Zeile einen offenen Paren vergessen. Ich habe es hinzugefügt. – Steve

Antwort

8

Die Notwendigkeit des Hinzufügens von Capturing und Backtracking-Maschinen ist einer der Nachteile, die Regexp::Grammars Adressen.

Die Grammatik in Ihrer Frage ist jedoch left-recursive, die weder von Perl noch von einem rekursiven Abstiegsparser parsen.

Anpassung Ihre Grammatik zu Regexp::Grammars und links Rekursion erzeugt

my $EXPR = do { 
    use Regexp::Grammars; 
    qr{ 
    ^<Expr> $ 

    <rule: Expr>  <Term> <ExprTail> 
       |  <Term> 

    <rule: Term>  <Number> 
       |  <Var> 
       |  \(<MATCH=Expr> \) 

    <rule: ExprTail> <Op> <Expr> 

    <token: Op>   \+ | \- | \* | \/ 

    <token: Number>  \d++ 

    <token: Var>  [a-z]++ 
    }x; 
}; 

Hinweis Ausklammern, dass diese einfache Grammatik eher alle Betreiber gleich Vorrang gibt, als Sie bitte meine liebe Tante Sally Entschuldigung.

Sie möchten alle Variablennamen extrahieren, so könnten Sie die AST als in

sub all_variables { 
    my($root,$var) = @_; 

    $var ||= {}; 
    ++$var->{ $root->{Var} } if exists $root->{Var}; 
    all_variables($_, $var) for grep ref $_, values %$root; 

    wantarray ? keys %$var : [ keys %$var ]; 
} 

und drucken Sie das Ergebnis mit

if ("(a + (b - c))" =~ $EXPR) { 
    print "[$_]\n" for sort +all_variables \%/; 
} 
else { 
    print "no match\n"; 
} 

Ein weiterer Ansatz ist zu Fuß eine autoaction für die Var Regel installieren Das zeichnet Namen von Variablen auf, wie sie erfolgreich analysiert werden.

package JustTheVarsMaam; 

sub new { bless {}, shift } 

sub Var { 
    my($self,$result) = @_; 
    ++$self->{VARS}{$result}; 
    $result; 
} 

sub all_variables { keys %{ $_[0]->{VARS} } } 

1; 

nennt diese wie in

my $vars = JustTheVarsMaam->new; 
if ("(a + (b - c))" =~ $EXPR->with_actions($vars)) { 
    print "[$_]\n" for sort $vars->all_variables; 
} 
else { 
    print "no match\n"; 
} 

So oder so, ist die Ausgabe

[a] 
[b] 
[c]
7

Rekursivität stammt mit Marpa::R2 die BNF im __DATA__ Abschnitt unter Verwendung der nachstehend:

#!env perl 
use strict; 
use diagnostics; 
use Marpa::R2; 

my $input = shift || '(a + (b - c))'; 

my $grammar_source = do {local $/; <DATA>}; 
my $recognizer = Marpa::R2::Scanless::R->new 
    (
    { 
    grammar => Marpa::R2::Scanless::G->new 
    (
    { 
     source => \$grammar_source, 
     action_object => __PACKAGE__, 
    } 
    ) 
    }, 
); 
my %vars =(); 
sub new { return bless {}, shift;} 
sub varAction { ++$vars{$_[1]}}; 

$recognizer->read(\$input); 
$recognizer->value() || die "No parse"; 

print join(', ', sort keys %vars) . "\n"; 

__DATA__ 
:start ::= expr 
expr ::= NUMBER 
     | VAR action => varAction 
     | expr OP expr 
     | '(' expr ')' 
NUMBER ~ [\d]+ 
VAR ~ [a-z]+ 
OP ~ [-+*/] 
WS ~ [\s]+ 
:discard ~ WS 

Die Ausgabe lautet:

a, b, c 

Ihre Frage nur wurde praxisnah erläutert, wie die Variablennamen zu bekommen, also keine Ahnung von Operatorassoziativität und so weiter in dieser Antwort. Beachten Sie, dass Marpa bei Bedarf kein Problem damit hat.

Verwandte Themen