Raku: l'effet des marqueurs de capture est perdu «plus haut»
Le script Raku suivant:
#!/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!"');
a la sortie suivante:
「foo = 42」
keyword => 「foo」
value => 「42」
numvalue => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「"Hello, World!"」
strvalue => 「Hello, World!」
Pour le deuxième point, notez que strvalue
contient la valeur de chaîne sans guillemets, comme prévu avec les marchés de capture <(
... )>
. Cependant, à ma grande surprise, les citations sont incluses dans value
.
Y a-t-il un moyen de contourner ceci?
Réponses
TL; DR Utiliser "envoi multiple". [1,2] Voir la réponse de @ user0721090601 pour une explication approfondie de la raison pour laquelle les choses sont telles qu'elles sont. Voir @ p6steve pour un changement vraiment intelligent de votre grammaire si vous voulez que votre syntaxe numérique corresponde à celle de Raku.
Une solution d'expédition multiple
Y a-t-il un moyen de contourner ceci?
Une façon est de passer à une distribution multiple explicite.
Vous disposez actuellement d'un value
jeton qui appelle des variantes de valeur spécifiquement nommées:
token value { <strvalue> | <numvalue> }
Remplacez cela par:
proto token value {*}
puis renommez les jetons appelés selon les règles de ciblage de distribution multiple de grammaire, de sorte que la grammaire devient:
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!"');
Cela affiche:
「foo = 42」
keyword => 「foo」
value => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「Hello, World!」
Cela ne capture pas les alternances individuelles par défaut. Nous pouvons nous en tenir à la "répartition multiple" mais réintroduire la dénomination des sous-captures:
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!"');
affiche:
「foo = 42」
keyword => 「foo」
value => 「42」
numvalue => 「42」
「bar = "Hello, World!"」
keyword => 「bar」
value => 「Hello, World!」
strvalue => 「Hello, World!」
Surprises
à ma grande surprise, les citations sont incluses dans
value
.
Moi aussi, j'ai été surpris au début. [3]
Mais le comportement actuel a également un sens pour moi au moins dans les sens suivants:
Le comportement existant a du mérite dans certaines circonstances;
Ce ne serait pas surprenant si je m'y attendais, ce que je pense que j'aurais bien pu faire dans d'autres circonstances;
Il est difficile de voir comment on pourrait obtenir le comportement actuel si elle était voulu mais a travaillé comme vous (et moi) initialement prévu;
Il existe une solution, comme indiqué ci-dessus.
Notes de bas de page
[1] L' utilisation de l'envoi multiple [2] est une solution, mais semble trop complexe au vu du problème d'origine. Il existe peut-être une solution plus simple. Peut-être que quelqu'un le fournira dans une autre réponse à votre question. Sinon, j'espère que nous aurons un jour au moins une solution beaucoup plus simple. Cependant, je ne serais pas surpris si nous n'en obtenons pas pendant de nombreuses années. Nous avons la solution ci-dessus, et il y a encore beaucoup à faire.
[2] Bien que vous puissiez déclarer, par exemple,method value:foo { ... }
et écrire une méthode (à condition que chacune de ces méthodes retourne un objet de correspondance), je ne pense pas que Rakudo utilise le mécanisme habituel de répartition de méthodes multiples pour envoyer des alternances de règles non-méthode, mais utilise plutôt un NFA .
[3] Certains pourraient argumenter que cela "devrait", "pourrait" ou "serait" "pour le mieux" si Raku faisait ce que nous attendions. Je trouve que je pense que mes meilleures pensées si j'évite généralement [sh | c | w] ould à propos de bogues / fonctionnalités à moins que je ne sois prêt à prendre tous les inconvénients que les autres soulèvent et je suis prêt à aider à faire le travail nécessaire pour obtenir Choses faites. Je dirai donc simplement que je le vois actuellement comme un bogue à 10%, une fonctionnalité à 90%, mais "pourrait" passer à un bogue à 100% ou à une fonctionnalité à 100% selon que je veux ce comportement ou non dans un scénario donné , et en fonction de ce que les autres pensent.
Les marqueurs de capture <(
et )>
ne fonctionnent que dans un jeton donné. Fondamentalement, chaque jeton retourne un Match
objet qui dit "J'ai fait correspondre la chaîne d'origine de l'index X ( .from
) à l'index Y ( .to
)", qui est pris en compte lors de la stringification des Match
objets. C'est ce qui se passe avec votre jeton strvalue:
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!
Vous remarquerez qu'il n'y a que deux nombres: une valeur de début et une valeur de fin. Cet homme que lorsque vous regardez le value
jeton que vous avez, il ne peut pas créer une correspondance non contiguë. Il .from
est donc réglé sur 6 et .to
sur 21.
Il y a deux façons de contourner cela: en utilisant (a) un objet actions ou (b) un multi-jeton. Les deux ont leurs avantages, et selon la façon dont vous souhaitez l'utiliser dans un projet plus vaste, vous voudrez peut-être opter pour l'un ou l'autre.
Bien que vous puissiez techniquement définir des actions directement dans une grammaire, il est beaucoup plus facile de les faire via une classe distincte. Nous pourrions donc avoir pour vous:
class MyActions {
method TOP ($/) { make $<keyword>.made => $<value>.made }
method keyword ($/) { make ~$/ }
method value ($/) { make ($<numvalue> // $<strvalue>).made } method numvalue ($/) { make +$/ } method strvalue ($/) { make ~$/ }
}
Chaque niveau make
pour transmettre des valeurs à n'importe quel jeton l'inclut. Et le jeton englobant a accès à leurs valeurs via la .made
méthode. C'est vraiment bien quand, au lieu de travailler avec des valeurs de chaîne pures, vous voulez les traiter d'abord d'une manière ou d'une autre et créer un objet ou similaire.
Pour analyser, il vous suffit de faire:
my $m = MyGrammar.parse: $text, :actions(MyActions); say $m.made; # bar => Hello, World!
Ce qui est en fait un Pair
objet. Vous pouvez changer le résultat exact en modifiant la TOP
méthode.
La deuxième façon de contourner les choses est d'utiliser un fichier multi token
. Il est assez courant dans le développement de grammaires d'utiliser quelque chose qui ressemble à
token foo { <option-A> | <option-B> }
Mais comme vous pouvez le voir dans la classe d'actions, cela nous oblige à vérifier et voir laquelle correspond réellement. Au lieu de cela, si l'alternance peut être acceptable en terminé avec |
, vous pouvez utiliser un multi-jeton:
proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }
Lorsque vous l'utilisez <foo>
dans votre grammaire, il correspondra à l'une des deux versions multiples comme s'il avait été dans la ligne de base <foo>
. Mieux encore, si vous utilisez une classe d'actions, vous pouvez de la même manière utiliser $<foo>
et savoir qu'elle est là sans aucune condition ou autre vérification.
Dans votre cas, cela ressemblerait à ceci:
grammar MyGrammar
{
rule TOP { <keyword> '=' <value> }
token keyword { \w+ }
proto token value { * }
multi token value:sym<str> { '"' <( <-["]>* )> '"' }
multi token value:sym<num> { '-'? \d+ [ '.' \d* ]? }
}
Maintenant, nous pouvons accéder aux choses comme vous vous y attendiez à l'origine, sans utiliser d'objet actions:
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!」
Pour référence, vous pouvez combiner les deux techniques. Voici comment j'écrirais maintenant l'objet actions étant donné le jeton multi:
class MyActions {
method TOP ($/) { make $<keyword>.made => $<value>.made } method keyword ($/) { make ~$/ } method value:sym<str> ($/) { make ~$/ } method value:sym<num> ($/) { make +$/ }
}
Ce qui est un peu plus grokkable à première vue.
Plutôt que de rouler votre propre valeur de jeton: str & valeur de jeton: num, vous pouvez utiliser la vérification booléenne Regex pour la correspondance Num (+) et Str (~) - comme expliqué ici et documenté ici
token number { \S+ <?{ defined +"$/" }> } token string { \S+ <?{ defined ~"$/" }> }