2009-08-28 3 views
5

Tôi gặp sự cố khi thử nghiệm chức năng Combinator Combinator cho một cuốn sách DSL đơn giản.Các vấn đề về phân tích cú pháp Scala

Trước hết có một lớp cuốn sách:

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

Tiếp theo, đó là phân tích cú pháp đơn giản:

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

Tôi nhận được một loạt các sai sót cố gắng để biên dịch này - một số trong đó dường như một lạ với tôi khi cố gắng phá hủy các ví dụ khác trên internet. Ví dụ, hàm bookSpec xuất hiện gần giống với các ví dụ khác?

Đây có phải là cách tốt nhất để tạo một trình phân tích cú pháp đơn giản như thế này không?

Cảm ơn

Trả lời

15

Bạn đang đi đúng hướng. Có một vài vấn đề trong trình phân tích cú pháp của bạn. Tôi sẽ đăng mã đã sửa, sau đó giải thích các thay đổi.

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. giá trị Parser trở

Để trả về một cuốn sách từ một phân tích cú pháp, bạn cần phải cung cấp cho các loại inferencer một số giúp đỡ. Tôi đã thay đổi định nghĩa của hàm bookSpec thành rõ ràng: nó trả về một Trình phân tích cú pháp [Sách]. Nghĩa là, nó trả về một đối tượng là một trình phân tích cú pháp cho sách.

2. stringLit

Chức năng stringLit bạn sử dụng xuất phát từ đặc điểm StdTokenParsers. stringLit là một hàm trả về Parser [String], nhưng mẫu nó khớp với các dấu ngoặc kép mà hầu hết các ngôn ngữ sử dụng để phân định một chuỗi ký tự. Nếu bạn hài lòng với các từ kép trong DSL của bạn, thì stringLit là những gì bạn muốn. Vì lợi ích của sự đơn giản, tôi đã thay thế stringLit bằng ident. ident tìm kiếm một mã nhận dạng ngôn ngữ Java. Đây không thực sự là định dạng phù hợp cho ISBN, nhưng nó đã vượt qua trường hợp thử nghiệm của bạn. :-)

Để khớp chính xác ISBN, tôi nghĩ bạn sẽ cần phải sử dụng cụm từ regex thay vì idents.

3. Bỏ qua bên trái chuỗi

khớp của bạn sử dụng một chuỗi các ~> và tổ hợp. Đây là một hàm nhận hai đối tượng Parser [_] và trả về một Trình phân tích cú pháp nhận dạng cả hai theo thứ tự, sau đó trả về kết quả của phía bên tay phải. Bằng cách sử dụng một chuỗi toàn bộ chúng để dẫn đến chuỗi cuối cùng của bạn, trình phân tích cú pháp của bạn sẽ bỏ qua mọi thứ ngoại trừ từ cuối cùng trong câu. Điều đó có nghĩa là nó sẽ vứt bỏ tên sách.

Ngoài ra, khi bạn sử dụng ~> hoặc < ~, các mã thông báo bị bỏ qua sẽ không xuất hiện trong kết hợp mẫu của bạn.

Để đơn giản, tôi đã thay đổi tất cả thành các hàm chuỗi đơn giản và để lại các mã thông báo bổ sung trong khớp mẫu.

4. Kết hợp kết quả

Phương pháp kiểm tra cần phải phù hợp với tất cả các kết quả có thể từ parse() chức năng. Vì vậy, tôi đã thêm các trường hợp Thất bại() và Lỗi().Ngoài ra, ngay cả Thành công cũng bao gồm cả hai giá trị trả về của bạn và đối tượng Reader. Chúng tôi không quan tâm đến người đọc, vì vậy tôi chỉ sử dụng "_" để bỏ qua nó trong khớp mẫu.

Hy vọng điều này sẽ hữu ích!

+0

xuất sắc trả lời cảm ơn bạn - đã trải qua tất cả các sách Scala hiện tại và sắp tới, và đây là một câu trả lời tốt hơn so với hai Tôi có đối phó với nó (Martin Odersky của cũng như một đến từ Wampler & Payne) – ShaunL

6

Khi bạn sử dụng ~> hoặc <~, bạn đang loại bỏ phần tử mà từ đó mũi tên đến. Ví dụ:

"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 

Bạn có thể viết nó như thế này:

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

Cảm ơn bạn Daniel cho câu trả lời của bạn - như với mtnygard, câu trả lời là rất hữu ích cho tôi – ShaunL

+0

Tôi nghĩ rằng điều này là sai bây giờ, '<~ 'là kết hợp vì vậy nếu chúng ta có' "book" ~> stringLit <~ "có" <~ "isbn" ~ stringLi', nó tương đương với '" book "~> (stringLit <~ (" has "< ~ ("isbn" ~ stringLi))) 'do đó isbn sẽ không được trả về. Thử nghiệm với scala 2.9.2 –

+1

@ GuillaumeMassé Có một vấn đề, nhưng ưu tiên của bạn bị phá vỡ là sai. Chỉ có '~' có quyền ưu tiên cao hơn '<~', do đó, kết thúc của dòng trở thành '" có "<~ (" isbn "~ stringLit)'. Tôi đã thêm một tập hợp dấu ngoặc đơn để tránh dấu ngoặc đơn. Ngoài ra, nó có lẽ là sai tất cả thời gian này - đã không được ưu tiên thay đổi trong ngôn ngữ cho một thời gian khá. –