2017-11-05 3 views
6

Ich möchte lernen, wie man eine abstrakte Syntax-Struktur für verschachtelte Tupel mit einem Perl Regexp mit eingebetteten Code-Ausführung erstellen. Ich kann das leicht mit einer Perl 6-Grammatik programmieren, und mir ist klar, dass die Verwendung von Parsing-Modulen die Aufgabe in Perl 5 vereinfachen würde, aber ich denke, dass ich für solche einfachen Aufgaben ohne Module lernen könnte, wie man maschinell übersetzt Grammatikdefinitionen. Ich konnte keinen Weg zur Dereferenzierung von $^R finden, also versuche ich die unfreiwillige Verschachtelung am Ende der TUPLE-Regeldefinition rückgängig zu machen, aber die Ausgabe ist inkorrekt, z. Einige Teilstrings erscheinen zweimal.

use v5.10; 
use Data::Dumper; 

while (<DATA>) { 
    chomp; 
    /(?&TUPLE)(?{$a = $^R}) 
    (?(DEFINE) 
     (?<TUPLE> 
      T \s (?&ELEM) \s (?&ELEM) 
      (?{ [$^R->[0][0],[$^R->[0][1],$^R[1]]] }) 
     ) 
     (?<ELEM> 
      (?: (a) (?{ [$^R,$^N] }) | \((?&TUPLE) \)) 
     ) 
    )/x; 
    say Dumper $a; 
} 

__DATA__ 
T a a 
T (T a a) a 
T a (T a a) 
T (T a a) (T a a) 
T (T (T a a) a) (T a (T a a)) 

Erwartete Ausgangsdatenstruktur ist eine verschachtelte Liste:

['a','a']; 
['a',['a','a']]; 
[['a','a'],'a']; 
[['a','a'],['a','a']]; 
[[['a','a'],'a'],['a',['a','a']]] 

Als Referenz ich auch Perl 6 Code arbeiten meine teilen werden: um herauszufinden,

grammar Tuple { 
    token TOP { 'T ' <elem> ' ' <elem> } 
    token elem { 'a' | '(' <TOP> ')'} 
} 
class Actions { 
    method TOP($/) {make ($<elem>[0].made, $<elem>[1].made)} 
    method elem($/) {make $<TOP> ?? $<TOP>.made !! 'a'} 
} 

Antwort

6

Der Versuch, wie zu verwenden, um (?{ ... }) Konstrukte ist fast immer nicht die Mühe wert. Insbesondere kann dies zusammen mit dem Zurückverfolgen ein unerwartetes Verhalten haben. Es ist auch sehr schwierig, solche Regexe zu debuggen, da der Kontrollfluss dazu neigt, nicht offensichtlich zu sein.

Stattdessen ist es einfacher, einen Ad-hoc-rekursiven Descent-Parser mit m//gc-Stil Lexing zu schreiben: Jeder Perl-String speichert seinen letzten Match-Offset. Wenn ein Regex mit m/\G ... /gc im skalaren Kontext angewendet wird, kann er am letzten Offset verankern und den Offset weiterführen, wenn die Übereinstimmung erfolgreich ist.

hier:

use strict; 
use warnings; 
use Test::More; 

sub parse { 
    my ($str) = @_; 
    pos($str) = 0; # set match position to beginning 
    return parse_tuple(\$str); 
} 

sub parse_tuple { 
    my ($ref) = @_; 
    $$ref =~ /\G T \s/gcx or die error($ref, "expected tuple start T"); 
    my $car = parse_element($ref); 
    $$ref =~ /\G \s /gcx or die error($ref, "expected space between tuple elements"); 
    my $cdr = parse_element($ref); 
    return [$car, $cdr]; 
} 

sub parse_element { 
    my ($ref) = @_; 
    return 'a' if $$ref =~ /\G a /gcx; 

    $$ref =~ /\G \(/gcx or die error($ref, "expected opening paren for nested tuple"); 
    my $tuple = parse_tuple($ref); 
    $$ref =~ /\G \) /gcx or die error($ref, "expected closing paren after nested tuple"); 
    return $tuple; 
} 

sub error { 
    my ($ref, $msg) = @_; 
    my $snippet = substr $$ref, pos($$ref), 20; 
    return "$msg just before '$snippet...'"; 
} 

is_deeply parse('T a a'), ['a','a']; 
is_deeply parse('T (T a a) a'), [['a','a'],'a']; 
is_deeply parse('T a (T a a)'), ['a',['a','a']]; 
is_deeply parse('T (T a a) (T a a)'), [['a','a'],['a','a']]; 
is_deeply parse('T (T (T a a) a) (T a (T a a))'), [[['a','a'],'a'],['a',['a','a']]]; 
done_testing; 
+0

Cool, schreiben Sie es so macht es einfacher. Warum übergeben Sie den String-Parameter als Referenz in parse_tuple? – rubystallion

+2

@rubystallion Da das aktuelle 'pos', wo die' \ G'-Anker Teil des String-Wertes sind, dürfen wir keine Kopie machen (oder wir müssten 'pos' in jedem Sub erneut zuweisen). Beachten Sie, dass 'parse_element()' die Suche fortsetzen kann, wobei 'parse_tuple()' beendet ist, weil '$ ref 'die richtige Position hat. Kopien werden auch für größere Dokumente ineffizient. – amon

+1

Ich denke, das ist einer jener Orte, an denen es sinnvoll wäre, '$ _' anstelle eines Parameters zu verwenden. – ikegami

0

fixierte ich den Code in meiner Frage. Stellt sich heraus, dass ich versehentlich $^R[1] anstelle von $^R->[1] geschrieben habe. So, jetzt verstehe ich, warum amon sagte, dass diese Konstrukte schwer zu debuggen sind ;-)

use v5.10; 
use Data::Dumper; 

while (<DATA>) { 
    chomp; 
    /(?&TUPLE)(?{$a = $^R->[1]}) 
    (?(DEFINE) 
     (?<TUPLE> 
      T \s (?&ELEM) \s (?&ELEM) 
      (?{ [$^R->[0][0],[$^R->[0][1],$^R->[1]]] }) 
     ) 
     (?<ELEM> 
      (?: (a) (?{ [$^R,$^N] }) | \((?&TUPLE) \)) 
     ) 
    )/x; 
    say Dumper $a; 
} 

__DATA__ 
T a a 
T (T a a) a 
T a (T a a) 
T (T a a) (T a a) 
T (T (T a a) a) (T a (T a a))