2009-08-28 5 views
5

Sto riscontrando problemi nell'esaminare le funzionalità di Scala Parser Combinator per un semplice DSL libro.Problemi di Parser di Scala

In primo luogo v'è una classe libro:

case class Book (name:String,isbn:String) { 
def getNiceName():String = name+" : "+isbn 
} 

Avanti, c'è la semplice parser:

object BookParser extends StandardTokenParsers { 
    lexical.reserved += ("book","has","isbn") 

    def bookSpec = "book" ~> stringLit ~> "has" ~> "isbn" ~> stringLit ^^ { 
      case "book" ~ name ~ "has" ~ "isbn" ~ isbn => new Book(name,isbn) } 

    def parse (s: String) = { 
    val tokens = new lexical.Scanner(s) 
    phrase(bookSpec)(tokens) 
    } 

    def test (exprString : String) = { 
    parse (exprString) match { 
     case Success(book) => println("Book"+book.getNiceName()) 
    } 
    } 

    def main (args: Array[String]) = { 
    test ("book ABC has isbn DEF") 
    } 
} 

sto ottenendo una serie di errori che cercano di compilare questo - alcuni che sembrano un strano per me quando provo a decostruire gli altri esempi su internet. Ad esempio, la funzione bookSpec appare quasi identica agli altri esempi?

È questo il modo migliore per creare un parser semplice come questo?

Grazie

risposta

15

Sei sulla strada giusta. Ci sono alcuni problemi nel parser. Pubblicherò il codice corretto, quindi spiegherò le modifiche.

import scala.util.parsing.combinator._ 
import scala.util.parsing.combinator.syntactical._ 

case class Book (name: String, isbn: String) { 
    def niceName = name + " : " + isbn 
} 


object BookParser extends StandardTokenParsers { 
    lexical.reserved += ("book","has","isbn") 

    def bookSpec: Parser[Book] = "book" ~ ident ~ "has" ~ "isbn" ~ ident ^^ { 
      case "book" ~ name ~ "has" ~ "isbn" ~ isbn => new Book(name, isbn) } 

    def parse (s: String) = { 
    val tokens = new lexical.Scanner(s) 
    phrase(bookSpec)(tokens) 
    } 

    def test (exprString : String) = { 
    parse (exprString) match { 
     case Success(book, _) => println("Book: " + book.niceName) 
     case Failure(msg, _) => println("Failure: " + msg) 
     case Error(msg, _) => println("Error: " + msg) 
    } 
    } 

    def main (args: Array[String]) = { 
    test ("book ABC has isbn DEF") 
    } 
} 

1. valore di ritorno Parser

Al fine di restituire un libro da un parser, è necessario dare il tipo inferencer qualche aiuto. Ho modificato la definizione della funzione bookSpec in modo esplicito: restituisce un parser [Libro]. Cioè, restituisce un oggetto che è un parser per i libri.

2. stringLit

La funzione stringLit si è utilizzato proviene dalla StdTokenParsers trait. stringLit è una funzione che restituisce Parser [String], ma il modello che corrisponde include le virgolette utilizzate dalla maggior parte delle lingue per delimitare un letterale stringa. Se sei soddisfatto delle parole con doppie virgolette nel tuo DSL, stringLit è ciò che desideri. Nell'interesse della semplicità, ho sostituito stringLit con ident. ident cerca un identificatore di linguaggio Java. Questo non è davvero il formato giusto per i codici ISBN, ma ha superato il tuo caso di test. :-)

Per far corrispondere correttamente i codici ISBN, penso che sarà necessario utilizzare un'espressione regolare anziché idents.

3. sequenza ignore-sinistra

tuo matcher utilizzato una serie di ~> combinatori. Questa è una funzione che prende due oggetti Parser [_] e restituisce un parser che riconosce entrambi in sequenza, quindi restituisce il risultato del lato destro. Usando tutta una catena di essi per arrivare alla stringa finaleLit, il parser ignorerebbe tutto tranne l'ultima parola nella frase. Ciò significa che butterebbe via anche il nome del libro.

Inoltre, quando si utilizza ~> o < ~, i token ignorati non dovrebbero apparire nella corrispondenza del modello.

Per semplicità, ho modificato tutto in semplici funzioni di sequenza e ho lasciato i token extra nel pattern match.

4. Risultati corrispondenti

Il metodo di prova deve corrispondere tutti i possibili risultati della funzione() parse. Quindi, ho aggiunto i casi Failure() e Error().Inoltre, anche Success include sia il valore restituito e l'oggetto Reader. Non ci interessa il lettore, quindi ho semplicemente usato "_" per ignorarlo nel pattern match.

Spero che questo aiuti!

+0

eccellente risposta vi ringrazio - sono state passando attraverso tutti i libri Scala attuali e futuri, e questo è una risposta migliore rispetto alle due Ho un accordo con questo (quello di Martin Odersky e quello di Wampler e Payne) – ShaunL

6

Quando si utilizza ~> o <~, si sta scartando l'elemento da cui proviene la freccia. Per esempio:

"book" ~> stringLit // discards "book" 
"book" ~> stringLit ~> "has" // discards "book" and then stringLit 
"book" ~> stringLit ~> "has" ~> "isbn" // discards everything except "isbn" 
"book" ~> stringLit ~> "has" ~> "isbn" ~> stringLit // discards everything but the last stringLit 

si potrebbe scrivere in questo modo:

def bookSpec: Parser[Book] = ("book" ~> stringLit <~ "has" <~ "isbn") ~ stringLit ^^ { 
    case name ~ isbn => new Book(name,isbn) 
} 
+0

Grazie Daniel per la tua risposta - come con mtnygard, la risposta mi è molto utile – ShaunL

+0

Penso che ora sia sbagliato, '<~ 'è associativo quindi se abbiamo' "libro" ~> stringaLit <~ "ha" <~ "isbn" ~ stringaLi' equivale a '" libro "~> (stringLit <~ (" ha "< ~ ("isbn" ~ stringLi))) "così isbn non sarà restituito. Testato con scala 2.9.2 –

+1

@ GuillaumeMassé C'è un problema, ma la tua precedente interruzione è sbagliata. È solo che '~' ha precedenza più alta su '<~', quindi la fine della riga diventa '" ha "<~ (" isbn "~ stringaLit)'. Ho aggiunto una serie di parentesi per evitarlo. Inoltre, probabilmente era sbagliato tutto questo tempo - non ci sono stati cambiamenti di precedenza nella lingua per un po 'di tempo. –