Raku: l'effetto dei segnalini di cattura viene perso "più in alto"

Aug 15 2020

Il seguente script Raku:

#!/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!"');

ha il seguente output:

「foo = 42」
 keyword => 「foo」
 value => 「42」
  numvalue => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「"Hello, World!"」
  strvalue => 「Hello, World!」

Per il secondo punto, nota che strvaluecontiene il valore della stringa senza virgolette, come previsto con i mercati di cattura <(... )>. Tuttavia, con mia sorpresa, le virgolette sono incluse in value.

C'è un modo per aggirare questo?

Risposte

6 raiph Aug 15 2020 at 22:30

TL; DR Usa "invio multiplo". [1,2] Vedi la risposta di @ user0721090601 per una spiegazione approfondita del perché le cose sono come sono. Vedi @ p6steve's per un cambiamento davvero intelligente alla tua grammatica se vuoi che la sintassi dei tuoi numeri corrisponda a quella di Raku.

Una soluzione di spedizione multipla

C'è un modo per aggirare questo?

Un modo è passare all'invio multiplo esplicito.

Attualmente disponi di un valuetoken che chiama varianti di valore specificatamente denominate:

    token value { <strvalue> | <numvalue> }

Sostituiscilo con:

    proto token value {*}

e quindi rinominare i token chiamati in base alle regole di targeting di invio multiplo della grammatica, quindi la grammatica diventa:

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!"');

Questo mostra:

「foo = 42」
 keyword => 「foo」
 value => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「Hello, World!」

Questo non cattura le singole alternanze per impostazione predefinita. Possiamo attenerci alla "spedizione multipla" ma reintrodurre la denominazione delle subacquisizioni:

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!"');

visualizza:

「foo = 42」
 keyword => 「foo」
 value => 「42」
  numvalue => 「42」
「bar = "Hello, World!"」
 keyword => 「bar」
 value => 「Hello, World!」
  strvalue => 「Hello, World!」

Sorprese

con mia grande sorpresa, le citazioni sono incluse in value.

Anch'io inizialmente ero sorpreso. [3]

Ma il comportamento attuale ha senso anche per me almeno nei seguenti sensi:

  • Il comportamento esistente ha valore in alcune circostanze;

  • Non sarebbe sorprendente se me lo aspettavo, cosa che penso avrei potuto fare in altre circostanze;

  • Non è facile vedere come si otterrebbe il comportamento corrente se lo si desidera, ma invece ha funzionato come inizialmente previsto;

  • C'è una soluzione, come spiegato sopra.

Note a piè di pagina

[1] L' uso dell'invio multiplo [2] è una soluzione, ma sembra eccessivamente complesso dato il problema originale. Forse c'è una soluzione più semplice. Forse qualcuno lo fornirà in un'altra risposta alla tua domanda. In caso contrario, spero che un giorno avremo almeno una soluzione molto più semplice. Tuttavia, non sarei sorpreso se non ne ottenessimo uno per molti anni. Abbiamo la soluzione di cui sopra e c'è molto altro da fare.

[2] Sebbene sia possibile dichiarare, diremethod value:foo { ... }e scrivere un metodo (a condizione che ciascuno di questi metodi restituisca un oggetto match), non credo che Rakudo utilizzi il solito meccanismo di invio di metodi multipli per inviare ad alternanze di regole non di metodo ma invece NFA .

[3] Alcuni potrebbero sostenere che "dovrebbe", "potrebbe" o "sarebbe" "essere per il meglio" se Raku facesse come ci aspettavamo. Trovo che i miei migliori pensieri se generalmente evito [sh | c | w] di bug / funzionalità a meno che non sia disposto a prendere in considerazione tutti gli aspetti negativi che gli altri sollevano e sono disposto ad aiutare a fare il lavoro necessario per ottenere cose fatte. Quindi dirò solo che attualmente lo vedo come un bug del 10%, una funzionalità del 90%, ma "potrebbe" passare al bug del 100% o alla funzionalità del 100% a seconda che io voglia o meno quel comportamento in un dato scenario e in base a ciò che pensano gli altri.

6 user0721090601 Aug 15 2020 at 22:46

I marcatori <(e )>cattura funzionano solo all'interno di un dato gettone. Fondamentalmente, ogni token restituisce un Matchoggetto che dice "Ho abbinato la stringa originale dall'indice X ( .from) all'indice Y ( .to)", che viene preso in considerazione durante la stringa degli Matchoggetti. Ecco cosa sta succedendo con il tuo token 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!

Noterai che ci sono solo due numeri: un valore iniziale e uno finale. Questo uomo che quando guardi il valuegettone che hai, non può creare una corrispondenza discontinua. Quindi .fromè impostato su 6 e .tosu 21.

Ci sono due modi per aggirare questo problema: usando (a) un oggetto azioni o (b) un multitoken. Entrambi hanno i loro vantaggi e, a seconda di come vuoi usarlo in un progetto più ampio, potresti optare per l'uno o l'altro.

Sebbene sia possibile definire tecnicamente azioni direttamente all'interno di una grammatica, è molto più semplice eseguirle tramite una classe separata. Quindi potremmo avere per te:

class MyActions { 
  method TOP      ($/) { make $<keyword>.made => $<value>.made }
  method keyword  ($/) { make ~$/ }
  method value    ($/) { make ($<numvalue> // $<strvalue>).made } method numvalue ($/) { make +$/ } method strvalue ($/) { make ~$/ }
}

Ogni livello makeper passare valori fino a qualsiasi token lo includa. E il token che lo racchiude ha accesso ai propri valori tramite il .mademetodo. Questo è davvero bello quando, invece di lavorare con valori stringa puri, vuoi prima elaborarli in qualche modo e creare un oggetto o simile.

Per analizzare, devi solo fare:

my $m = MyGrammar.parse: $text, :actions(MyActions); say $m.made; # bar => Hello, World!

Che in realtà è un Pairoggetto. È possibile modificare il risultato esatto modificando il TOPmetodo.

Il secondo modo in cui puoi aggirare le cose è usare un file multi token. È abbastanza comune nello sviluppo di grammatiche usare qualcosa di simile

token foo { <option-A> | <option-B> }

Ma come puoi vedere dalla classe di azioni, ci richiede di controllare e vedere quale è stata effettivamente abbinata. Invece, se l'alternanza può essere accettata |, puoi utilizzare un multitoken:

proto token foo { * }
multi token:sym<A> { ... }
multi token:sym<B> { ... }

Quando lo usi <foo>nella tua grammatica, corrisponderà a una delle due versioni multiple come se fosse nella linea di base <foo>. Ancora meglio, se stai usando una classe di azioni, puoi semplicemente usarla $<foo>e sapere che è lì senza condizionali o altri controlli.

Nel tuo caso, sarebbe simile a questo:

grammar MyGrammar
{
    rule TOP { <keyword> '=' <value> }
    token keyword { \w+ }
    proto token value { * }
    multi token value:sym<str> { '"' <( <-["]>* )> '"' }
    multi token value:sym<num> { '-'? \d+ [ '.' \d* ]? }
}

Ora possiamo accedere alle cose come ti aspettavi originariamente, senza utilizzare un oggetto azioni:

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!」

Per riferimento, puoi combinare entrambe le tecniche. Ecco come scriverei ora l'oggetto azioni dato il multi token:

class MyActions { 
  method TOP            ($/) { make $<keyword>.made => $<value>.made } method keyword ($/) { make ~$/ } method value:sym<str> ($/) { make ~$/ } method value:sym<num> ($/) { make +$/ }
}

Che è un po 'più comprensibile a prima vista.

2 p6steve Aug 16 2020 at 03:13

Piuttosto che rotolare il tuo valore token: str e valore token: num potresti voler usare il controllo booleano Regex per la corrispondenza Num (+) e Str (~) - come spiegato qui e documentato qui

token number { \S+ <?{ defined +"$/" }> } token string { \S+ <?{ defined ~"$/" }> }