Raku: Der Effekt von Capture-Markern geht "höher" verloren.

Aug 15 2020

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 strvalueder 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

6 raiph Aug 15 2020 at 22:30

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 valueToken, 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.

6 user0721090601 Aug 15 2020 at 22:46

Die Markierungen <(und )>erfassen funktionieren nur innerhalb eines bestimmten Tokens. Grundsätzlich gibt jedes Token ein MatchObjekt 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 MatchObjekten 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 valueTokens, den Sie haben, keine nicht zusammenhängende Übereinstimmung erstellt werden kann. Es .fromist also auf 6 und .toauf 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 .madeMethode 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 PairObjekt. Sie können das genaue Ergebnis ändern, indem Sie die TOPMethode ä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.

2 p6steve Aug 16 2020 at 03:13

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 ~"$/" }> }