Raku: Der Effekt von Capture-Markern geht "höher" verloren.
Das folgende Raku-Skript:
#!/usr/bin/env raku
use v6.d;
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
token value { <strvalue> | <numvalue> }
token strvalue { '"' <( <-["]>* )> '"' }
token numvalue { '-'? \d+ [ '.' \d* ]? }
}
say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');
hat folgende Ausgabe:
「foo = 42」
keyword => 「foo」
value => 「42」
numvalue => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「"Hello, World!"」
strvalue => 「Hello, World!」
Für den zweiten Punkt zu beachten , dass strvalue
der String - Wert ohne Anführungszeichen enthält, wie mit den Fänger Märkten bestimmt <(
... )>
. Zu meiner Überraschung sind die Zitate jedoch in enthalten value
.
Gibt es einen Weg, dies zu umgehen?
Antworten
TL; DR Verwenden Sie "Mehrfachversand". [1,2] In der Antwort von @ user0721090601 finden Sie eine ausführliche Erklärung, warum die Dinge so sind, wie sie sind. Unter @ p6steve finden Sie eine wirklich clevere Änderung Ihrer Grammatik, wenn Sie möchten, dass Ihre Zahlensyntax mit der von Raku übereinstimmt.
Eine Mehrfachversandlösung
Gibt es einen Weg, dies zu umgehen?
Eine Möglichkeit besteht darin, auf expliziten Mehrfachversand umzuschalten.
Sie haben derzeit ein value
Token, das speziell benannte Wertvarianten aufruft:
token value { <strvalue> | <numvalue> }
Ersetzen Sie das durch:
proto token value {*}
und benennen Sie dann die aufgerufenen Token gemäß den Grammatikregeln für das Mehrfachversand-Targeting um, sodass die Grammatik wie folgt lautet:
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
proto token value {*}
token value:str { '"' <( <-["]>* )> '"' }
token value:num { '-'? \d+ [ '.' \d* ]? }
}
say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');
Dies zeigt an:
「foo = 42」
keyword => 「foo」
value => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「Hello, World!」
Dies erfasst standardmäßig nicht die einzelnen Wechsel. Wir können bei "Mehrfachversand" bleiben, aber die Benennung der Untererfassungen wieder einführen:
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
proto token value { * }
token value:str { '"' <( $<strvalue>=(<-["]>*) )> '"' } token value:num { $<numvalue>=('-'? \d+ [ '.' \d* ]?) }
}
say MyGrammar.parse('foo = 42');
say MyGrammar.parse('bar = "Hello, World!"');
zeigt an:
「foo = 42」
keyword => 「foo」
value => 「42」
numvalue => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「Hello, World!」
strvalue => 「Hello, World!」
Überraschungen
Zu meiner Überraschung sind die Zitate in enthalten
value
.
Auch ich war anfangs überrascht. [3]
Das aktuelle Verhalten macht für mich aber auch zumindest in folgender Hinsicht Sinn:
Das bestehende Verhalten hat unter bestimmten Umständen seine Berechtigung;
Es wäre nicht überraschend, wenn ich es erwartet hätte, was ich unter anderen Umständen wohl getan hätte.
Es ist nicht leicht zu sehen , wie man würde das aktuelle Verhalten, wenn es wurde gesucht , sondern arbeitet als Sie (und ich) zunächst erwartet;
Es gibt eine Lösung, wie oben beschrieben.
Fußnoten
[1] Die Verwendung von Mehrfachversand [2] ist eine Lösung, erscheint jedoch angesichts des ursprünglichen Problems imo übermäßig komplex. Vielleicht gibt es eine einfachere Lösung. Vielleicht gibt es jemand in einer anderen Antwort auf Ihre Frage. Wenn nicht, würde ich hoffen, dass wir eines Tages mindestens eine viel einfachere Lösung haben. Es würde mich jedoch nicht wundern, wenn wir seit vielen Jahren keine mehr bekommen. Wir haben die oben genannte Lösung und es gibt noch viel zu tun.
[2] Während Sie können erklären, sagen sie,method value:foo { ... }
und ein Verfahren schreiben (jeder dieser Methode gibt ein MatchObjektVerfügung gestellt), ich glaube nichtRakudo den üblichen Verfahren mehr DispatchMechanismus zum Versand verwendetum nicht-Methode Abwechslungen Regelsondern verwendet stattdessen eine NFA .
[3] Einige könnten argumentieren, dass es "sollte", "könnte" oder "würde" "das Beste sein", wenn Raku das tat, was wir erwartet hatten. Ich denke, ich denke, meine besten Gedanken, wenn ich es generell vermeide, [sh | c | w] über Fehler / Funktionen nachzudenken, es sei denn, ich bin bereit, alle Nachteile zu berücksichtigen, die andere in Betracht ziehen, und bin bereit, bei derErledigungder dafür erforderlichen Arbeit zu helfen Dinge erledigt. Ich sage also nur, dass ich es derzeit als 10% Fehler, 90% Feature sehe, aber "könnte" zu 100% Fehler oder 100% Feature wechseln, je nachdem, ob ich dieses Verhalten in einem bestimmten Szenario möchte oder nicht und je nachdem, was andere denken.
Die Markierungen <(
und )>
erfassen funktionieren nur innerhalb eines bestimmten Tokens. Grundsätzlich gibt jedes Token ein Match
Objekt zurück, das besagt, dass "Ich habe die ursprüngliche Zeichenfolge von Index X ( .from
) mit Index Y ( .to
) abgeglichen ", was bei der Zeichenfolge von Match
Objekten berücksichtigt wird . Das passiert mit Ihrem strvalue-Token:
my $text = 'bar = "Hello, World!"'; my $m = MyGrammar.parse: $text; my $start = $m<value><strvalue>.from; # 7 my $end = $m<value><strvalue>.to; # 20 say $text.substr: $start, $end - $start; # Hello, World!
Sie werden feststellen, dass es nur zwei Zahlen gibt: einen Start- und einen Endwert. Dies bedeutet, dass beim Betrachten des value
Tokens, den Sie haben, keine nicht zusammenhängende Übereinstimmung erstellt werden kann. Es .from
ist also auf 6 und .to
auf 21 eingestellt.
Es gibt zwei Möglichkeiten, dies zu umgehen: Verwenden von (a) einem Aktionsobjekt oder (b) einem Multitoken. Beide haben ihre Vorteile, und je nachdem, wie Sie dies in einem größeren Projekt verwenden möchten, möchten Sie sich möglicherweise für das eine oder andere entscheiden.
Während Sie Aktionen direkt in einer Grammatik technisch definieren können, ist es viel einfacher, sie über eine separate Klasse auszuführen. Also könnten wir für Sie haben:
class MyActions {
method TOP ($/) { make $<keyword>.made => $<value>.made }
method keyword ($/) { make ~$/ }
method value ($/) { make ($<numvalue> // $<strvalue>).made } method numvalue ($/) { make +$/ } method strvalue ($/) { make ~$/ }
}
Jede Ebene make
, um Werte an ein beliebiges Token zu übergeben, enthält sie. Und das einschließende Token hat über die .made
Methode Zugriff auf ihre Werte . Dies ist sehr schön, wenn Sie nicht nur mit reinen Zeichenfolgenwerten arbeiten, sondern diese zuerst auf irgendeine Weise verarbeiten und ein Objekt oder ähnliches erstellen möchten.
Zum Parsen tun Sie einfach:
my $m = MyGrammar.parse: $text, :actions(MyActions); say $m.made; # bar => Hello, World!
Welches ist eigentlich ein Pair
Objekt. Sie können das genaue Ergebnis ändern, indem Sie die TOP
Methode ändern .
Die zweite Möglichkeit, Dinge zu umgehen, ist die Verwendung von a multi token
. Bei der Entwicklung von Grammatiken ist es ziemlich üblich, etwas Ähnliches zu verwenden
token foo { <option-A> | <option-B> }
Wie Sie jedoch aus der Aktionsklasse ersehen können, müssen wir überprüfen, welche tatsächlich übereinstimmt. Wenn die Abwechslung akzeptabel ist |
, können Sie stattdessen ein Multitoken verwenden:
proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }
Wenn Sie <foo>
in Ihrer Grammatik verwenden, stimmt es mit einer der beiden Multi-Versionen überein, als ob es in der Grundlinie gewesen wäre <foo>
. Noch besser ist, wenn Sie eine Aktionsklasse verwenden, können Sie diese auch einfach verwenden $<foo>
und wissen, dass sie ohne Bedingungen oder andere Überprüfungen vorhanden ist.
In Ihrem Fall würde es so aussehen:
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
proto token value { * }
multi token value:sym<str> { '"' <( <-["]>* )> '"' }
multi token value:sym<num> { '-'? \d+ [ '.' \d* ]? }
}
Jetzt können wir auf Dinge zugreifen, wie Sie es ursprünglich erwartet hatten, ohne ein Aktionsobjekt zu verwenden:
my $text = 'bar = "Hello, World!"';
my $m = MyGrammar.parse: $text;
say $m; # 「bar = "Hello, World!"」 # keyword => 「bar」 # value => 「Hello, World!」 say $m<value>; # 「Hello, World!」
Als Referenz können Sie beide Techniken kombinieren. So würde ich jetzt das Aktionsobjekt mit dem Multi-Token schreiben:
class MyActions {
method TOP ($/) { make $<keyword>.made => $<value>.made } method keyword ($/) { make ~$/ } method value:sym<str> ($/) { make ~$/ } method value:sym<num> ($/) { make +$/ }
}
Was auf den ersten Blick etwas grokkbarer ist.
Anstatt Ihren eigenen Token-Wert zu rollen: str & Token-Wert: num, möchten Sie möglicherweise die Regex-Boolesche Prüfung für die Übereinstimmung von Num (+) und Str (~) verwenden - wie mir hier erklärt und hier dokumentiert
token number { \S+ <?{ defined +"$/" }> } token string { \S+ <?{ defined ~"$/" }> }