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 String
và execute
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ố "hello
và world"
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
và - 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).
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
@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. –