2009-08-28 10 views
5

Mam problemy z testowaniem funkcji scalania parsera Scala dla prostej książki DSL.Problemy z analizatorem parsera Scala

Po pierwsze istnieje klasa książka:

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

Następny istnieje prosty 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") 
    } 
} 

Dostaję szereg błędów próbuje skompilować ten - takie, które wydaje się dziwne dla mnie, gdy próbuję zdekonstruować inne przykłady w Internecie. Na przykład funkcja bookSpec wygląda prawie identycznie jak inne przykłady?

Czy to najlepszy sposób na zbudowanie prostego parsera?

Dzięki

Odpowiedz

15

Jesteś na dobrej drodze. W twoim parserze jest kilka problemów. Opublikuję poprawiony kod, a następnie wytłumaczę zmiany.

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. Wartość zwracana Parser

W celu powrotu książkę z parsera, trzeba podać typ inferencer pomoc. Zmieniłem definicję funkcji bookSpec tak, aby była jawna: zwraca Parser [Book]. Oznacza to, że zwraca obiekt, który jest analizatorem składni dla książek.

2. stringLit

Funkcja stringLit użyłeś pochodzi z cechą StdTokenParsers. stringLit to funkcja, która zwraca Parser [String], ale dopasowany wzorzec zawiera podwójne cudzysłowy, których większość języków używa do ograniczania literału łańcuchowego. Jeśli jesteś zadowolony z podwójnego cytowania słów w twoim DSL, wtedy stringLit jest tym, czego potrzebujesz. Dla uproszczenia zastąpiłem stringLit identyfikatorem. Ident wyszukuje identyfikator w języku Java. Nie jest to właściwy format dla numerów ISBN, ale minął test. :-)

Aby poprawnie dopasować numery ISBN, myślę, że będziesz potrzebować użyć wyrażenia regularnego zamiast identyfikatora.

3. ignorowanych lewo sekwencja

Twój dopasowujący używany ciąg ~> sumatory. Jest to funkcja, która pobiera dwa obiekty Parser [_] i zwraca Parser, który rozpoznaje oba w kolejności, a następnie zwraca wynik po prawej stronie. Używając całego łańcucha, który doprowadzi do ostatecznego stringLit, twój parser zignorowałby wszystko oprócz ostatniego słowa w zdaniu. Oznacza to, że wyrzuci też nazwę książki.

Również, gdy używasz ~> lub < ~, ignorowane tokeny nie powinny pojawiać się w dopasowaniu do wzorca.

Dla uproszczenia zmieniłem te wszystkie na proste funkcje sekwencyjne i pozostawiłem dodatkowe żetony w dopasowaniu wzorca.

4. Dalsze wyniki

Metoda badawcza musi pasować do wszystkich możliwych wynika z funkcji() przetworzenia. Dodałem więc przypadki Failure() i Error().Ponadto, nawet Sukces zawiera zarówno swoje wartości zwracanej i obiekt Reader. Nie interesuje nas czytelnik, więc użyłem "_", aby zignorować go w dopasowaniu wzorca.

Mam nadzieję, że to pomoże!

+0

Doskonała odpowiedź dziękuję - zostały przechodząc przez wszystkich obecnych i przyszłych książek Scala, i jest to lepsze rozwiązanie niż dwa mam który sobie z tym poradzić (Martin Odersky, jak również tego, pochodzących z Wampler & Payne) – ShaunL

6

Podczas korzystania z ~> lub <~, odrzucasz element, z którego pochodzi strzałka. Na przykład:

"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 

Można napisać to tak:

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

Dziękuję Daniel za odpowiedź - jak z mtnygard, odpowiedź jest bardzo pomocne dla mnie – ShaunL

+0

myślę, że to jest źle teraz, '<~ 'jest łączne, więc jeśli mamy' "książkę" ~> stringLit <~ "ma" <~ "ISBN" ~ stringLi' jest to równoważne ' "książki" ~> (stringLit <~ ("ma"< ~ ("isbn" ~ stringLi)))) w ten sposób isbn nie zostanie zwrócony. Testowane z Scala 2.9.2 –

+1

@ GuillaumeMassé Jest to problem, ale twój pierwszeństwo przełamać to błędne. Po prostu '~' ma wyższy priorytet niż '<~', więc koniec linii staje się "" ma "<~ (" isbn "~ stringLit). Dodałem zestaw nawiasów, aby tego uniknąć. Co więcej, prawdopodobnie przez cały czas był błędny - od jakiegoś czasu nie nastąpiły pierwsze zmiany w języku. –