2013-03-05 18 views
39

Khi viết một phân tích cú pháp trong một thư viện parser combinator như Parsec Haskell, bạn thường có 2 lựa chọn:Tôi có nên sử dụng lexer khi sử dụng thư viện trình kết hợp phân tích cú pháp như Parsec không?

  • Viết lexer chia đầu vào String của bạn vào thẻ, sau đó thực hiện phân tích trên [Token]
  • Trực tiếp viết combinators phân tích cú pháp trên String

Phương pháp đầu tiên thường có vẻ hợp lý vì nhiều đầu vào phân tích cú pháp có thể được hiểu là mã thông báo được phân tách bằng khoảng trắng.

người

Ở những nơi khác, tôi đã thấy khuyên bạn nên chống tokenizing (hoặc quét hoặc lexing, làm thế nào một số gọi nó), với sự đơn giản được trích dẫn là lý do chính.

Sự cân bằng chung giữa lexing và không làm gì?

+3

Theo kinh nghiệm của tôi, nó phụ thuộc vào ngôn ngữ bạn đang phân tích cú pháp. Một số ngôn ngữ nhất định là một nỗi đau thực sự để phân tích cú pháp mà không bị lexing đầu tiên. – Pubby

+1

@Pubby, không, không có ngôn ngữ nào như vậy. Bất kỳ ngôn ngữ có thể được phân tích cú pháp hoàn hảo mà không có một lexer. –

Trả lời

50

Sự khác biệt quan trọng nhất là lexing sẽ dịch miền nhập của bạn.

Một đẹp kết quả của việc này là

  • Bạn không cần phải suy nghĩ về khoảng trắng nữa. Trong trình phân tích cú pháp trực tiếp (không phải lexing), bạn phải rải các trình phân tích cú pháp space ở tất cả các vị trí mà khoảng trắng được phép, dễ bị quên và nó cắt mã của bạn nếu khoảng trắng phải tách riêng tất cả mã thông báo của bạn.

  • Bạn có thể nghĩ về đầu vào của mình theo từng phần, dễ dàng cho con người.

Tuy nhiên, nếu bạn làm thực hiện lexing, bạn sẽ có được vấn đề rằng

  • Bạn không thể sử dụng phân tích cú pháp phổ biến trên String nữa - ví dụ để phân tích cú pháp một số với thư viện Function parseFloat :: Parsec String s Float (hoạt động trên luồng đầu vào String), bạn phải làm một cái gì đó như takeNextToken :: TokenParser Stringexecute trình phân tích cú pháp parseFloat trên đó, kiểm tra kết quả phân tích cú pháp (thường là Either ErrorMessage a). Điều này là lộn xộn để viết và giới hạn tính composability.

  • Bạn đã điều chỉnh tất cả các thông báo lỗi. Nếu trình phân tích cú pháp của bạn trên mã thông báo không thành công tại mã thông báo thứ 20, trong đó chuỗi đầu vào ở đâu? Bạn sẽ phải tự ánh xạ các vị trí lỗi trở lại chuỗi đầu vào, đó là tẻ nhạt (trong Parsec điều này có nghĩa là điều chỉnh tất cả các giá trị SourcePos).

  • Báo cáo lỗi thường tồi tệ hơn. Chạy string "hello" *> space *> float trên đầu vào sai như "hello4" sẽ cho bạn biết chính xác rằng có khoảng trắng được mong đợi bị thiếu sau số hello, trong khi một từ khoá sẽ chỉ xác nhận đã tìm thấy "invalid token".

  • Nhiều thứ mà người ta mong đợi là đơn vị nguyên tử và được phân tách bằng một từ khóa thực sự là "quá khó" đối với một từ khóa để xác định. Lấy ví dụ về các chuỗi ký tự - đột nhiên "hello world" không phải là hai mã số "helloworld" nữa (nhưng chỉ, tất nhiên, nếu dấu ngoặc kép không được thoát, như là \") - điều này có nghĩa là các quy tắc phức tạp và đặc biệt trường hợp cho một lexer.

  • Bạn không thể sử dụng lại trình phân tích cú pháp trên mã thông báo là độc đáo. Nếu bạn xác định cách phân tích cú pháp gấp đôi trong số String, hãy xuất nó và phần còn lại của thế giới có thể sử dụng nó; họ không thể chạy tokenizer (chuyên dụng) của bạn trước tiên.

  • Bạn bị kẹt với nó. Khi bạn đang phát triển ngôn ngữ để phân tích cú pháp, sử dụng từ vựng có thể dẫn bạn đưa ra quyết định sớm, sửa chữa những thứ mà bạn có thể muốn thay đổi sau đó. Ví dụ: hãy tưởng tượng bạn đã xác định ngôn ngữ chứa một số mã thông báo Float. Tại một thời điểm nào đó, bạn muốn giới thiệu các chữ cái âm (-3.4- 3.4) - điều này có thể không thực hiện do lexer giải thích khoảng trắng là dấu phân cách dấu hiệu. Sử dụng phương pháp phân tích cú pháp chỉ, bạn có thể linh hoạt hơn, thay đổi ngôn ngữ của bạn dễ dàng hơn. Điều này không thực sự đáng ngạc nhiên vì trình phân tích cú pháp là một công cụ phức tạp hơn vốn đã mã hóa các quy tắc .

Để tóm tắt, tôi khuyên bạn nên viết trình phân tích cú pháp không có lexer cho hầu hết các trường hợp.

Cuối cùng, một lexer chỉ là một trình phân tích cú pháp "bị dumbed" * - nếu bạn cần một trình phân tích cú pháp, hãy kết hợp chúng thành một.


* Từ lý thuyết tính toán, chúng tôi biết rằng all regular languages are also context-free languages; lexers thường là thường xuyên, phân tích cú pháp không có bối cảnh hoặc thậm chí bối cảnh nhạy cảm (các trình phân tích cú pháp đơn thuần như Parsec có thể thể hiện tính nhạy cảm theo ngữ cảnh).

+4

Điều gì về hiệu suất? Tôi đoán nếu bạn đang sử dụng Parsec anyhow hiệu suất không phải là tối quan trọng, nhưng nó vẫn là một xem xét có thể. –

+2

Một vấn đề tiềm năng khác với một từ vựng chuyên dụng là bạn sẽ không thể triển khai các trình phân tích cú pháp mở rộng (với các bộ mã thông báo khác) nữa. –

+2

Câu trả lời hay, nhưng tôi phải đưa ra vấn đề với "đơn vị nguyên tử khó trong một ví dụ". Bây giờ tôi chắc chắn không phải là một chuyên gia về lý thuyết, nhưng tôi tin rằng các chuỗi phân cách có thể được phân tích cú pháp khá dễ dàng với một ngữ pháp thông thường. tức là '/ ^" ([^ \\ "] | \\") * "/' là một biểu thức chính quy thực sự (theo ý thức chính thức - tôi nghĩ) mà thậm chí có giao dịch với việc trốn thoát. Mặc dù vậy, vấn đề được thực hiện tốt. –

Các vấn đề liên quan