Проблемы с Scala Parser - PullRequest
       30

Проблемы с Scala Parser

5 голосов
/ 28 августа 2009

У меня проблемы с тестированием функциональности Scala Parser Combinator для простого Book DSL.

Во-первых, есть книжный класс:

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

Далее, есть простой парсер:

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")
  }   
}

Я получаю ряд ошибок, пытаясь скомпилировать это - некоторые из них кажутся мне странными, когда я пытаюсь разобрать другие примеры в Интернете. Например, функция bookSpec выглядит практически идентично другим примерам?

Это лучший способ создать простой парсер, подобный этому?

Спасибо

Ответы [ 2 ]

15 голосов
/ 28 августа 2009

Вы на правильном пути. В вашем парсере есть несколько проблем. Я опубликую исправленный код, затем объясню изменения.

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. Возвращаемое значение парсера

Чтобы вернуть книгу из анализатора, вам нужно помочь выводителю типа. Я изменил определение функции bookSpec, чтобы оно было явным: оно возвращает Parser [Book]. То есть он возвращает объект, который является парсером для книг.

2. stringLit

Используемая вами функция stringLit взята из черты StdTokenParsers. stringLit - это функция, которая возвращает Parser [String], но соответствующий ей шаблон включает в себя двойные кавычки, которые большинство языков используют для разделения строкового литерала. Если вас устраивают двойные кавычки в вашем DSL, то вам нужен stringLit. В целях простоты я заменил stringLit на идент. id ищет идентификатор языка Java. Это не совсем правильный формат для ISBN, но он прошел ваш тестовый пример. : -)

Чтобы правильно сопоставить номера ISBN, я думаю, вам нужно использовать выражение регулярных выражений вместо идентификаторов.

3. Игнорировать левая последовательность

Ваш сопоставитель использовал строку ~> сумматоров. Это функция, которая принимает два объекта Parser [_] и возвращает Parser, который распознает оба по порядку, а затем возвращает результат с правой стороны. Используя целую цепочку из них, чтобы привести к вашему последнему stringLit, ваш синтаксический анализатор будет игнорировать все, кроме последнего слова в предложении. Это означает, что это также выбросило бы название книги.

Кроме того, когда вы используете ~> или <~, игнорируемые токены не должны появляться в вашем сопоставлении с образцом. </p>

Для простоты я изменил их все на простые функции последовательности и оставил дополнительные токены в сопоставлении с образцом.

4. Подходящие результаты

Метод тестирования должен соответствовать всем возможным результатам функции parse (). Итак, я добавил случаи Failure () и Error (). Кроме того, даже успех включает в себя и возвращаемое значение и объект Reader. Нас не волнует читатель, поэтому я просто использовал «_», чтобы игнорировать его в сопоставлении с образцом.

Надеюсь, это поможет!

6 голосов
/ 28 августа 2009

Когда вы используете ~> или <~, вы отбрасываете элемент, из которого идет стрелка. Например:

"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

Вы могли бы написать это так:

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