2013-07-18 8 views
5

Sto cercando un modo per associare più righe Parslet. Il codice è simile al seguente:Prezzemolo rubino: analisi di più righe

rule(:line) { (match('$').absent? >> any).repeat >> match('$') } 
rule(:lines) { line.repeat } 

Tuttavia, lines sarà sempre finire in un ciclo infinito che è perché match('$') sarà infinitamente fanno in modo che corrisponda fine della stringa.

È possibile associare più righe che possono essere vuote?

irb(main)> lines.parse($stdin.read) 
This 
is 

a 
multiline 

string^D 

deve corrispondere correttamente. Mi sto perdendo qualcosa? Ho anche provato (match('$').absent? >> any.maybe).repeat(1) >> match('$') ma non corrisponde alle righe vuote.

Saluti,
Danyel.

risposta

3

Penso che tu abbia due, relativi, problemi con la vostra corrispondenza:

  • La partita pseudo-carattere $ non consuma personaggi reali. Hai comunque bisogno di consumare le newline in qualche modo.

  • Parslet esegue l'input in qualche modo, rendendo la corrispondenza $ in luoghi che potresti non aspettarti. Il miglior risultato che ho potuto ottenere usando $ ha finito per abbinare ogni singolo personaggio.

molto più sicuro di utilizzare \n come il carattere di fine linea. Ho ottenuto il seguente al lavoro (io sono un po 'un principiante con parslet me stesso, quindi scuse se potrebbe essere più chiaro):

require 'parslet' 

class Lines < Parslet::Parser 
    rule(:text) { match("[^\n]") } 
    rule(:line) { (text.repeat(0) >> match("\n")) | text.repeat(1) } 
    rule(:lines) { line.as(:line).repeat } 
    root :lines 
end 

s = "This 
is 

a 
multiline 
string" 

p Lines.new.parse(s) 

La regola per la linea è complessa a causa della necessità di adeguare le linee vuote e un possibile linea finale senza \n.

Non è necessario utilizzare la sintassi .as(:line) - l'ho appena aggiunto per mostrare chiaramente che la regola :line corrisponde a ciascuna riga singolarmente e non consuma semplicemente l'intero input.

+0

Questa sembra una buona soluzione. La mia soluzione era di lavorare anche con '\ n' e di aggiungere una nuova riga alla stringa in entrata per evitare un errore di corrispondenza alla fine. Questo sembra più pulito, però. Grazie! – Danyel

6

In genere definisco una regola per end_of_line. Questo è basato sul trucco in http://kschiess.github.io/parslet/tricks.html per la corrispondenza end_of_file.

class MyParser < Parslet::Parser 
    rule(:cr)   { str("\n") } 
    rule(:eol?)  { any.absent? | cr } 
    rule(:line_body) { (eol?.absent? >> any).repeat(1) } 
    rule(:line)  { cr | line_body >> eol? } 
    rule(:lines?)  { line.repeat (0)} 
    root(:lines?) 
end 

puts MyParser.new.parse(""" this is a line 
so is this 

that was too 
This ends""").inspect 

Ovviamente se si vuole fare di più con il parser di quanto si possa ottenere con String :: split ("\ n") si sostituire il line_body con qualcosa di utile :)


I ha fatto un rapido tentativo di rispondere a questa domanda e l'ha fatto saltare in aria. Io solo vorrei spiegare l'errore che ho fatto e mostrarti come evitare errori di questo tipo.

Ecco la mia prima risposta.

rule(:eol) { str('\n') | any.absent? } 
rule(:line) { (eol.absent? >> any).repeat >> eol } 
rule(:lines) { line.as(:line).repeat } 

non ho seguito le mie solite regole:

  • Assicurarsi sempre numero di ripetizioni esplicito
  • Qualsiasi regola che può corrispondere a zero stringhe di lunghezza, dovrebbe avere nome che termina con un '?'

Quindi, consente di applicare questi ...

rule(:eol?) { str('\n') | any.absent? } 
# as the second option consumes nothing 

rule(:line?) { (eol.absent? >> any).repeat(0) >> eol? } 
# repeat(0) can consume nothing 

rule(:lines?) { line.as(:line?).repeat(0) } 
# We have a problem! We have a rule that can consume nothing inside a `repeat`! 

Qui vedono perché otteniamo un ciclo infinito. Man mano che l'input viene consumato, si ottiene solo lo end of file, che corrisponde allo eol? e quindi allo line? (poiché il corpo della linea può essere vuoto). Essendo all'interno di lines 'repeat, mantiene la corrispondenza senza consumare nulla e loop per sempre.

Abbiamo bisogno di cambiare la regola della linea in modo che consuma sempre qualcosa.

rule(:cr)   { str('\n') } 
rule(:eol?)  { cr | any.absent? } 
rule(:line_body) { (eol.absent? >> any).repeat(1) } 
rule(:line)  { cr | line_body >> eol? } 
rule(:lines?)  { line.as(:line).repeat(0) } 

Ora line deve corrispondere qualcosa, o un cr (per le linee vuote), o almeno un carattere seguito dal opzionale eol?. Tutti gli repeat hanno corpi che consumano qualcosa. Siamo ora d'oro.

+0

Questo si trasforma in un ciclo infinito per me. – Danyel

+0

oops. sì, lo aggiusterò –

+0

I loop infiniti si verificano quando si hanno regole che possono corrispondere senza consumare alcun input. Qui 'line' corrisponde a una riga vuota, seguita dalla versione' any.absent? 'Di' eol' che non consuma nulla, quindi può mantenere la corrispondenza. –