Khi nào nên sử dụng Bộ kết hợp phân tích cú pháp? Khi nào nên sử dụng Trình tạo phân tích cú pháp?


59

Gần đây tôi đã đi sâu vào thế giới của các trình phân tích cú pháp, muốn tạo ngôn ngữ lập trình của riêng tôi.

Tuy nhiên, tôi phát hiện ra rằng có hai cách tiếp cận khác nhau khi viết trình phân tích cú pháp: Trình tạo phân tích cú pháp và Trình kết hợp phân tích cú pháp.

Thật thú vị, tôi đã không thể tìm thấy bất kỳ tài nguyên nào giải thích trong trường hợp nào phương pháp nào tốt hơn; Thay vào đó, nhiều nguồn lực (và người) tôi hỏi về vấn đề này không biết các cách tiếp cận khác, chỉ giải thích của họ tiếp cận như các cách tiếp cận và không nhắc đến khác tại tất cả:

  • Các cuốn sách Rồng nổi tiếng đi vào lexing / quét và đề cập đến (f) lex, nhưng không đề cập Parser combinators ở tất cả.
  • Các mẫu triển khai ngôn ngữ phụ thuộc rất nhiều vào Trình tạo phân tích cú pháp ANTLR được xây dựng bằng Java và hoàn toàn không đề cập đến Bộ kết hợp phân tích cú pháp.
  • Hướng dẫn Giới thiệu về Parsec về Parsec, là Trình kết hợp phân tích cú pháp trong Haskell, hoàn toàn không đề cập đến Trình tạo phân tích cú pháp.
  • Boost :: Spirit , Bộ kết hợp phân tích cú pháp C ++ nổi tiếng nhất, hoàn toàn không đề cập đến Trình tạo phân tích cú pháp.
  • Bài đăng trên blog giải thích tuyệt vời mà bạn có thể đã phát minh ra Bộ kết hợp phân tích cú pháp không đề cập đến Trình tạo phân tích cú pháp nào cả.

Tổng quan đơn giản:

Trình phân tích cú pháp

Trình tạo phân tích cú pháp lấy một tệp được viết bằng DSL là một phương ngữ của dạng Backus-Naur mở rộng và biến nó thành mã nguồn có thể (khi được biên dịch) trở thành trình phân tích cú pháp cho ngôn ngữ đầu vào được mô tả trong DSL này.

Điều này có nghĩa là quá trình biên dịch được thực hiện theo hai bước riêng biệt. Thật thú vị, chính Trình tạo Parser cũng là trình biên dịch (và nhiều trong số chúng thực sự là tự lưu trữ ).

Bộ kết hợp phân tích cú pháp

Bộ kết hợp bộ phân tích cú pháp mô tả các hàm đơn giản được gọi là bộ phân tích cú pháp , tất cả đều lấy đầu vào làm tham số và cố gắng loại bỏ (các) ký tự đầu tiên của đầu vào này nếu chúng khớp. Họ trả về một tuple (result, rest_of_input), nơi resultcó thể trống (ví dụ nilhoặc Nothing) nếu trình phân tích cú pháp không thể phân tích bất cứ điều gì từ đầu vào này. Một ví dụ sẽ là một digittrình phân tích cú pháp. Các trình phân tích cú pháp khác tất nhiên có thể lấy các trình phân tích cú pháp làm đối số đầu tiên (đối số cuối cùng vẫn là chuỗi đầu vào) để kết hợp chúng: ví dụ: many1cố gắng khớp với trình phân tích cú pháp khác nhiều lần nhất có thể (nhưng ít nhất một lần, hoặc chính nó thất bại).

Bây giờ bạn có thể kết hợp (soạn thảo) digitmany1, để tạo một trình phân tích cú pháp mới, giả sử integer.

Ngoài ra, một trình choicephân tích cú pháp cấp cao hơn có thể được viết để lấy danh sách các trình phân tích cú pháp, lần lượt thử từng trình phân tích cú pháp.

Theo cách này, các trình phân tích / phân tích cú pháp rất phức tạp có thể được xây dựng. Trong các ngôn ngữ hỗ trợ quá tải toán tử, nó cũng trông rất giống EBNF, mặc dù nó vẫn được viết trực tiếp bằng ngôn ngữ đích (và bạn có thể sử dụng tất cả các tính năng của ngôn ngữ đích mà bạn mong muốn).

Sự khác biệt đơn giản

Ngôn ngữ:

  • Trình tạo phân tích cú pháp được viết bằng sự kết hợp của DSL EBNF-ish và mã mà các câu lệnh này sẽ tạo ra khi chúng khớp.
  • Bộ kết hợp phân tích cú pháp được viết bằng ngôn ngữ đích trực tiếp.

Phân tích / phân tích cú pháp:

  • Trình tạo phân tích cú pháp có một sự khác biệt rất khác biệt giữa 'lexer' (phân tách một chuỗi thành các mã thông báo có thể được gắn thẻ để hiển thị loại giá trị nào chúng ta đang xử lý) và 'trình phân tích cú pháp' (lấy danh sách đầu ra của mã thông báo từ lexer và cố gắng kết hợp chúng, tạo thành Cây Cú pháp Trừu tượng).
  • Bộ kết hợp phân tích cú pháp không có / cần sự phân biệt này; thông thường, các trình phân tích cú pháp đơn giản thực hiện công việc của 'lexer' và các trình phân tích cú pháp cấp cao hơn gọi các trình phân tích đơn giản hơn này để quyết định loại nút AST nào sẽ tạo.

Câu hỏi

Tuy nhiên, ngay cả khi có những khác biệt này (và đây là danh sách các khác biệt có lẽ còn lâu mới hoàn thành!), Tôi không thể đưa ra lựa chọn có giáo dục về việc khi nào nên sử dụng cái nào. Tôi không thấy những tác động / hậu quả của những khác biệt này.

Những thuộc tính vấn đề nào sẽ chỉ ra rằng một vấn đề sẽ được giải quyết tốt hơn bằng cách sử dụng Trình tạo phân tích cú pháp? Những thuộc tính vấn đề nào sẽ chỉ ra rằng một vấn đề sẽ được giải quyết tốt hơn bằng cách sử dụng và Bộ kết hợp phân tích cú pháp?


4
Có ít nhất hai cách triển khai trình phân tích cú pháp mà bạn không đề cập: trình thông dịch trình phân tích cú pháp (tương tự như trình tạo trình phân tích cú pháp, ngoại trừ thay vì biên dịch ngôn ngữ trình phân tích cú pháp thành C hoặc Java, ngôn ngữ trình phân tích cú pháp được thực thi trực tiếp) và chỉ cần viết phân tích cú pháp bằng tay. Viết trình phân tích cú pháp bằng tay là hình thức triển khai ưa thích cho nhiều triển khai ngôn ngữ cường độ công nghiệp sẵn sàng sản xuất hiện đại (ví dụ: GCC, Clang javac, Scala). Nó cung cấp cho bạn quyền kiểm soát nhiều nhất đối với trạng thái trình phân tích cú pháp nội bộ, giúp tạo ra các thông báo lỗi tốt (trong những năm gần đây
LỚN

3
Sọ đã trở thành một ưu tiên rất cao cho những người thực hiện ngôn ngữ). Ngoài ra, nhiều trình tạo / phiên dịch / trình kết hợp trình phân tích cú pháp hiện tại không thực sự được thiết kế để đối phó với nhiều nhu cầu lớn mà việc triển khai ngôn ngữ hiện đại phải đáp ứng. Ví dụ, nhiều triển khai ngôn ngữ hiện đại sử dụng cùng một đoạn mã để biên dịch hàng loạt, biên dịch nền IDE, tô sáng cú pháp, tái cấu trúc tự động, hoàn thành mã thông minh, tạo tài liệu tự động, lập sơ đồ tự động, v.v. Scala thậm chí sử dụng trình biên dịch để phản ánh thời gian chạy và hệ thống macro của nó . Nhiều trình phân tích cú pháp hiện có
Jörg W Mittag

1
Khung công tác không đủ linh hoạt để đối phó với điều đó. Cũng lưu ý rằng có các khung trình phân tích cú pháp không dựa trên EBNF. Ví dụ: trình phân tích cú pháp packrat cho các ngữ pháp phân tích cú pháp .
Jörg W Mittag

2
Tôi nghĩ nó phụ thuộc rất nhiều vào ngôn ngữ bạn đang cố gắng biên dịch. Nó là loại gì (LR, ...)?
qwerty_so

1
Giả định của bạn ở trên dựa trên BNF, thường được biên dịch đơn giản với sự kết hợp của trình phân tích cú pháp lexer / LR. Nhưng ngôn ngữ không nhất thiết phải dựa trên ngữ pháp LR. Vì vậy, cái nào là của bạn mà bạn đang dự định biên dịch?
qwerty_so

Câu trả lời:


59

Tôi đã thực hiện rất nhiều nghiên cứu trong vài ngày qua, để hiểu rõ hơn tại sao những công nghệ riêng biệt này tồn tại, và điểm mạnh và điểm yếu của chúng là gì.

Một số câu trả lời đã tồn tại gợi ý về một số khác biệt của chúng, nhưng chúng không đưa ra bức tranh hoàn chỉnh, và dường như có phần ý kiến, đó là lý do tại sao câu trả lời này được viết.

Giải trình này dài, nhưng quan trọng. chịu đựng tôi (Hoặc nếu bạn không kiên nhẫn, hãy cuộn đến cuối để xem sơ đồ).


Để hiểu được sự khác biệt giữa Trình kết hợp phân tích cú pháp và Trình tạo phân tích cú pháp, trước tiên người ta cần hiểu sự khác biệt giữa các loại phân tích cú pháp tồn tại.

Phân tích cú pháp

Phân tích cú pháp là quá trình phân tích một chuỗi các ký hiệu theo một ngữ pháp chính thức. (Trong Khoa học máy tính), phân tích cú pháp được sử dụng để có thể cho phép máy tính hiểu văn bản được viết bằng ngôn ngữ, thường tạo ra một cây phân tích đại diện cho văn bản viết, lưu trữ ý nghĩa của các phần viết khác nhau trong mỗi nút của cây. Cây phân tích cú pháp này sau đó có thể được sử dụng cho nhiều mục đích khác nhau, chẳng hạn như dịch nó sang ngôn ngữ khác (được sử dụng trong nhiều trình biên dịch), diễn giải các hướng dẫn bằng văn bản theo cách nào đó (SQL, HTML), cho phép các công cụ như Linters thực hiện công việc của họ , v.v ... Đôi khi, một cây phân tích không rõ ràngđược tạo, nhưng thay vào đó, hành động nên được thực hiện tại mỗi loại nút trong cây được thực thi trực tiếp. Điều này làm tăng hiệu quả, nhưng dưới nước vẫn tồn tại một cây phân tích ngầm.

Phân tích cú pháp là một vấn đề khó tính toán. Đã có hơn năm mươi năm nghiên cứu về chủ đề này, nhưng vẫn còn nhiều điều để tìm hiểu.

Nói một cách đơn giản, có bốn thuật toán chung để cho phép máy tính phân tích cú pháp đầu vào:

  • LL phân tích cú pháp. (Không phân tích ngữ cảnh, phân tích từ trên xuống.)
  • Phân tích cú pháp LR. (Không phân tích ngữ cảnh, phân tích từ dưới lên.)
  • PEG + Phân tích cú pháp Packrat.
  • Phân tích Earley.

Lưu ý rằng các loại phân tích cú pháp là rất chung chung, mô tả lý thuyết. Có nhiều cách để thực hiện từng thuật toán này trên các máy vật lý, với sự đánh đổi khác nhau.

LL và LR chỉ có thể xem các ngữ pháp không ngữ cảnh (nghĩa là bối cảnh xung quanh các mã thông báo được viết không quan trọng để hiểu cách chúng được sử dụng).

Phân tích cú pháp PEG / Packrat và phân tích Earley được sử dụng ít hơn nhiều: Phân tích Earley rất hay ở chỗ nó có thể xử lý nhiều ngữ pháp hơn (bao gồm cả những ngữ pháp không nhất thiết phải là Ngữ cảnh) nhưng nó kém hiệu quả hơn (như được tuyên bố bởi rồng cuốn sách (phần 4.1.1); Tôi không chắc những tuyên bố này có còn chính xác không). Phân tích cú pháp ngữ pháp biểu thức + phân tích cú pháp là một phương pháp tương đối hiệu quả và cũng có thể xử lý nhiều ngữ pháp hơn cả LL và LR, nhưng ẩn đi sự mơ hồ, như sẽ nhanh chóng được chạm vào bên dưới.

LL (Đạo hàm từ trái sang phải, ngoài cùng bên trái)

Đây có thể là cách tự nhiên nhất để suy nghĩ về phân tích cú pháp. Ý tưởng là xem xét mã thông báo tiếp theo trong chuỗi đầu vào và sau đó quyết định một trong số nhiều cuộc gọi đệ quy có thể được thực hiện để tạo cấu trúc cây.

Cây này được xây dựng 'từ trên xuống', nghĩa là chúng ta bắt đầu từ gốc của cây và đi theo các quy tắc ngữ pháp giống như cách chúng ta đi qua chuỗi đầu vào. Nó cũng có thể được xem như là xây dựng một 'postfix' tương đương với luồng mã thông báo 'infix' đang được đọc.

Các trình phân tích cú pháp thực hiện phân tích cú pháp kiểu LL có thể được viết để trông rất giống với ngữ pháp gốc đã được chỉ định. Điều này làm cho nó tương đối dễ hiểu, gỡ lỗi và nâng cao chúng. Bộ kết hợp bộ phân tích cú pháp cổ điển không gì khác hơn là 'các mảnh lego' có thể được ghép lại để xây dựng bộ phân tích cú pháp kiểu LL.

LR (Đạo hàm từ trái sang phải, từ phải sang phải)

Phân tích cú pháp LR đi theo cách khác, từ dưới lên: Ở mỗi bước, (các) phần tử trên cùng của ngăn xếp được so sánh với danh sách ngữ pháp, để xem liệu chúng có thể được giảm xuống theo quy tắc cấp cao hơn trong ngữ pháp hay không. Nếu không, mã thông báo tiếp theo từ luồng đầu vào là shift ed và được đặt trên đỉnh của ngăn xếp.

Một chương trình là chính xác nếu cuối cùng chúng ta kết thúc với một nút duy nhất trên ngăn xếp đại diện cho quy tắc bắt đầu từ ngữ pháp của chúng ta.

Nhìn thẳng

Trong một trong hai hệ thống này, đôi khi cần phải xem lén nhiều mã thông báo hơn từ đầu vào trước khi có thể quyết định lựa chọn nào để thực hiện. Đây là (0), (1), (k)hoặc (*)-syntax bạn thấy sau khi tên của hai thuật toán này nói chung, chẳng hạn như LR(1) hay LL(k). kthường là viết tắt của 'nhiều như nhu cầu ngữ pháp của bạn', trong khi *thường là viết tắt của 'trình phân tích cú pháp này thực hiện quay lui', mạnh hơn / dễ thực hiện, nhưng có bộ nhớ và sử dụng thời gian cao hơn nhiều so với trình phân tích cú pháp có thể tiếp tục phân tích cú pháp tuyến tính.

Lưu ý rằng các trình phân tích cú pháp kiểu LR đã có nhiều mã thông báo trên ngăn xếp khi chúng có thể quyết định 'nhìn về phía trước', vì vậy chúng đã có thêm thông tin để gửi đi. Điều này có nghĩa là họ thường cần ít 'cái nhìn' hơn so với trình phân tích cú pháp kiểu LL cho cùng một ngữ pháp.

LL so với LR: Sự mơ hồ

Khi đọc hai mô tả ở trên, người ta có thể tự hỏi tại sao phân tích cú pháp kiểu LR tồn tại, vì phân tích cú pháp kiểu LL có vẻ tự nhiên hơn rất nhiều.

Tuy nhiên, phân tích cú pháp kiểu LL có một vấn đề: đệ quy trái .

Rất tự nhiên để viết một ngữ pháp như:

expr ::= expr '+' expr | term term ::= integer | float

Nhưng, trình phân tích cú pháp kiểu LL sẽ bị kẹt trong một vòng lặp đệ quy vô hạn khi phân tích ngữ pháp này: Khi thử khả năng bên trái của exprquy tắc, nó sẽ lặp lại quy tắc này mà không cần sử dụng bất kỳ đầu vào nào.

Có nhiều cách để giải quyết vấn đề này. Đơn giản nhất là viết lại ngữ pháp của bạn để loại đệ quy này không xảy ra nữa:

expr ::= term expr_rest expr_rest ::= '+' expr | ϵ term ::= integer | float (Ở đây, ϵ là viết tắt của 'chuỗi rỗng')

Ngữ pháp này bây giờ là đệ quy đúng. Lưu ý rằng nó ngay lập tức khó đọc hơn rất nhiều.

Trong thực tế, đệ quy trái có thể xảy ra gián tiếp với nhiều bước khác ở giữa. Điều này làm cho nó một vấn đề khó để tìm ra. Nhưng cố gắng giải quyết nó làm cho ngữ pháp của bạn khó đọc hơn.

Như Mục 2.5 của Sách Rồng nêu rõ:

Chúng ta dường như có một mâu thuẫn: một mặt chúng ta cần một ngữ pháp tạo điều kiện cho việc dịch thuật, mặt khác chúng ta cần một ngữ pháp khác biệt đáng kể tạo điều kiện cho việc phân tích cú pháp. Giải pháp là bắt đầu với ngữ pháp để dịch dễ dàng và cẩn thận chuyển đổi nó để tạo điều kiện phân tích cú pháp. Bằng cách loại bỏ đệ quy trái, chúng ta có thể có được một ngữ pháp phù hợp để sử dụng trong một dịch giả đệ quy gốc.

Các trình phân tích cú pháp kiểu LR không gặp vấn đề về đệ quy trái này, vì chúng xây dựng cây từ dưới lên. Tuy nhiên , bản dịch tinh thần của một ngữ pháp như trên sang trình phân tích cú pháp kiểu LR (thường được triển khai dưới dạng Tự động hóa trạng thái hữu hạn )
rất khó (và dễ bị lỗi), vì thường có hàng trăm hoặc hàng nghìn trạng thái + chuyển trạng thái để xem xét. Đây là lý do tại sao các trình phân tích cú pháp kiểu LR thường được tạo bởi Trình tạo phân tích cú pháp, còn được gọi là 'trình biên dịch trình biên dịch'.

Cách giải quyết sự mơ hồ

Chúng tôi đã thấy hai phương pháp để giải quyết các mơ hồ đệ quy bên trái ở trên: 1) viết lại cú pháp 2) sử dụng trình phân tích cú pháp LR.

Nhưng có nhiều loại mơ hồ khác khó giải quyết hơn: Điều gì xảy ra nếu hai quy tắc khác nhau được áp dụng như nhau cùng một lúc?

Một số ví dụ phổ biến là:

Cả hai trình phân tích cú pháp kiểu LL và kiểu LR đều có vấn đề với những điều này. Các vấn đề với phân tích biểu thức số học có thể được giải quyết bằng cách đưa ra ưu tiên toán tử. Theo cách tương tự, các vấn đề khác như Dangling Else có thể được giải quyết, bằng cách chọn một hành vi ưu tiên và gắn bó với nó. (Ví dụ, trong C / C ++, sự lơ lửng khác luôn thuộc về 'nếu' gần nhất).

Một "giải pháp" khác cho vấn đề này là sử dụng Ngữ pháp phân tích cú pháp (PEG): Điều này tương tự với ngữ pháp BNF được sử dụng ở trên, nhưng trong trường hợp mơ hồ, luôn luôn 'chọn đầu tiên'. Tất nhiên, điều này không thực sự 'giải quyết' vấn đề, mà chỉ che giấu rằng sự mơ hồ thực sự tồn tại: Người dùng cuối có thể không biết lựa chọn nào mà trình phân tích cú pháp đưa ra và điều này có thể dẫn đến kết quả không mong muốn.

Nhiều thông tin sâu hơn rất nhiều so với bài đăng này, bao gồm cả lý do tại sao nói chung không thể biết liệu ngữ pháp của bạn không có bất kỳ sự mơ hồ nào và ý nghĩa của bài viết này là bài viết tuyệt vời của LL và LR trong bối cảnh: Tại sao phân tích cú pháp công cụ rất khó . Tôi rất có thể giới thiệu nó; nó giúp tôi rất nhiều để hiểu tất cả những điều tôi đang nói bây giờ.

50 năm nghiên cứu

Nhưng cuộc sống vẫn tiếp diễn. Nó chỉ ra rằng các trình phân tích cú pháp kiểu 'bình thường' được triển khai như các máy tự động trạng thái hữu hạn thường cần hàng ngàn trạng thái + chuyển tiếp, đó là một vấn đề trong quy mô chương trình. Vì vậy, các biến thể như Simple LR (SLR) và LALR (Nhìn về phía trước) đã được viết kết hợp các kỹ thuật khác để làm cho máy tự động nhỏ hơn, giảm dung lượng đĩa và bộ nhớ của các chương trình phân tích cú pháp.

Ngoài ra, một cách khác để giải quyết các vấn đề mơ hồ được liệt kê ở trên là sử dụng các kỹ thuật tổng quát , trong trường hợp mơ hồ, cả hai khả năng đều được giữ và phân tích cú pháp: Một trong hai khả năng đó có thể không phân tích được dòng (trong trường hợp đó khả năng khác là 'đúng' một), cũng như trả lại cả hai (và theo cách này cho thấy sự mơ hồ tồn tại) trong trường hợp cả hai đều đúng.

Thật thú vị, sau khi thuật toán Generalized LR được mô tả, hóa ra một cách tiếp cận tương tự có thể được sử dụng để triển khai các trình phân tích LL tổng quát hóa , có độ phức tạp tương tự nhanh ($ O (n ^ 3) $ đối với các ngữ pháp mơ hồ, $ O (n) $ cho các ngữ pháp hoàn toàn không rõ ràng, mặc dù có nhiều sổ sách hơn so với trình phân tích cú pháp (LA) đơn giản, có nghĩa là hệ số không đổi cao hơn) nhưng một lần nữa cho phép trình phân tích cú pháp được viết theo kiểu đệ quy (từ trên xuống) tự nhiên hơn rất nhiều để viết và gỡ lỗi.

Trình kết hợp phân tích cú pháp, Trình tạo phân tích cú pháp

Vì vậy, với giải trình dài này, chúng tôi hiện đang đi đến cốt lõi của câu hỏi:

Sự khác biệt của Trình kết hợp phân tích cú pháp và Trình tạo phân tích cú pháp và khi nào nên sử dụng cái này so với cái khác?

Chúng thực sự là những loại quái vật khác nhau:

Bộ kết hợp phân tích cú pháp được tạo bởi vì mọi người đang viết trình phân tích cú pháp từ trên xuống và nhận ra rằng nhiều trong số này có nhiều điểm chung .

Trình tạo trình phân tích cú pháp được tạo vì mọi người đang tìm cách xây dựng trình phân tích cú pháp không có vấn đề mà trình phân tích cú pháp kiểu LL gặp phải (tức là trình phân tích cú pháp kiểu LR), điều này rất khó thực hiện bằng tay. Những cái phổ biến bao gồm Yacc / Bison, thực hiện (LA) LR).

Thật thú vị, ngày nay cảnh quan bị vấy bẩn phần nào:

  • Có thể viết Bộ kết hợp phân tích cú pháp hoạt động với thuật toán GLL , giải quyết sự mơ hồ - các vấn đề mà trình phân tích cú pháp kiểu LL cổ điển gặp phải, trong khi có thể đọc / hiểu được như tất cả các loại phân tích từ trên xuống.

  • Trình tạo phân tích cú pháp cũng có thể được viết cho trình phân tích cú pháp kiểu LL. ANTLR thực hiện chính xác điều đó và sử dụng các phương pháp phỏng đoán khác (Thích nghi LL (*)) để giải quyết sự mơ hồ mà các trình phân tích cú pháp kiểu LL cổ điển có.

Nói chung, việc tạo một trình tạo trình phân tích cú pháp LR và gỡ lỗi đầu ra của trình tạo trình phân tích cú pháp kiểu LR (LA) chạy trên ngữ pháp của bạn là khó khăn, do việc dịch ngữ pháp gốc của bạn sang dạng LR 'từ trong ra ngoài'. Mặt khác, các công cụ như Yacc / Bison đã có nhiều năm kinh optimisations, và nhìn thấy rất nhiều công dụng trong tự nhiên, có nghĩa là nhiều người bây giờ xem xét nó như là các cách để làm phân tích và hoài nghi đối với cách tiếp cận mới.

Bạn nên sử dụng cái nào, tùy thuộc vào mức độ khó của ngữ pháp của bạn và tốc độ của trình phân tích cú pháp. Tùy thuộc vào ngữ pháp, một trong những kỹ thuật này (/ triển khai các kỹ thuật khác nhau) có thể nhanh hơn, có dung lượng bộ nhớ nhỏ hơn, dấu chân đĩa nhỏ hơn hoặc dễ mở rộng hơn hoặc dễ gỡ lỗi hơn các kỹ thuật khác. Mileage của bạn có thể khác nhau .

Lưu ý bên lề: Về chủ đề Phân tích từ điển.

Phân tích từ điển có thể được sử dụng cho cả Trình kết hợp phân tích cú pháp và Trình tạo phân tích cú pháp. Ý tưởng là để có một trình phân tích cú pháp 'câm' rất dễ thực hiện (và do đó nhanh) thực hiện lần đầu tiên vượt qua mã nguồn của bạn, loại bỏ ví dụ lặp lại khoảng trắng, nhận xét, v.v. và có thể 'tokenizing' trong một cách thô các yếu tố khác nhau tạo nên ngôn ngữ của bạn.

Ưu điểm chính là bước đầu tiên này làm cho trình phân tích cú pháp thực sự đơn giản hơn rất nhiều (và vì điều đó có thể nhanh hơn). Nhược điểm chính là bạn có một bước dịch riêng biệt, và ví dụ báo cáo lỗi với số dòng và số cột trở nên khó khăn hơn do loại bỏ khoảng trắng.

Một lexer cuối cùng là 'chỉ' một trình phân tích cú pháp khác và có thể được thực hiện bằng bất kỳ kỹ thuật nào ở trên. Do tính đơn giản của nó, thường các kỹ thuật khác được sử dụng hơn so với trình phân tích cú pháp chính và ví dụ như các trình tạo 'lexer' tồn tại thêm.


Tl; Tiến sĩ:

Đây là một sơ đồ áp dụng cho hầu hết các trường hợp: nhập mô tả hình ảnh ở đây


@Sjoerd Nó thực sự là rất nhiều văn bản, vì nó hóa ra là một vấn đề rất khó khăn. Nếu bạn biết một cách mà tôi có thể làm cho đoạn cuối rõ ràng hơn, tôi sẽ nghe rõ: "Bạn nên sử dụng đoạn nào, tùy thuộc vào mức độ khó của ngữ pháp của bạn và trình phân tích cú pháp cần nhanh như thế nào. Tùy thuộc vào ngữ pháp, một trong những kỹ thuật này (/ triển khai các kỹ thuật khác nhau) có thể nhanh hơn, có dung lượng bộ nhớ nhỏ hơn, dung lượng đĩa nhỏ hơn hoặc dễ mở rộng hơn hoặc dễ gỡ lỗi hơn các kỹ thuật khác. Mileage May Vary. "
Qqwy

1
Các câu trả lời khác đều ngắn hơn và rõ ràng hơn nhiều, và thực hiện công việc trả lời tốt hơn nhiều.
Sjoerd

1
@Sjoerd lý do tôi viết câu trả lời này là vì các câu trả lời khác là quá đơn giản hóa vấn đề, đưa ra một câu trả lời một phần là câu trả lời đầy đủ và / hoặc rơi vào bẫy ngụy biện giai thoại . Câu trả lời ở trên là hiện thân của cuộc thảo luận Jörg W Mittag, Thomas Killian và tôi đã có trong các bình luận của câu hỏi sau khi hiểu những gì họ đang nói và trình bày nó mà không cần biết trước.
Qqwy

Trong mọi trường hợp, tôi đã thêm một sơ đồ tl; dr vào câu hỏi. Điều đó có làm bạn hài lòng không, @Sjoerd?
Qqwy

2
Bộ kết hợp phân tích cú pháp thực sự không giải quyết được vấn đề khi bạn không thực sự sử dụng chúng. Có nhiều tổ hợp hơn chỉ là |, đó là toàn bộ vấn đề. Viết lại chính xác cho exprthậm chí còn ngắn gọn hơn expr = term 'sepBy' "+"(trong đó các trích dẫn đơn ở đây đang thay thế cho backticks để biến một hàm trộn, bởi vì đánh dấu nhỏ không có ký tự thoát). Trong trường hợp tổng quát hơn cũng có chainBytổ hợp là tốt. Tôi nhận thấy rất khó để tìm một tác vụ phân tích cú pháp đơn giản như một ví dụ không phù hợp với PC, nhưng đó thực sự là một lập luận mạnh mẽ có lợi cho họ.
Steven Armstrong

8

Đối với đầu vào được đảm bảo không có lỗi cú pháp hoặc trong trường hợp vượt qua / thất bại về tính chính xác cú pháp là ổn, các bộ kết hợp trình phân tích cú pháp sẽ đơn giản hơn nhiều để làm việc, đặc biệt là trong các ngôn ngữ lập trình chức năng. Đây là những tình huống như câu đố lập trình, đọc tệp dữ liệu, v.v.

Tính năng khiến bạn muốn thêm độ phức tạp của trình tạo trình phân tích cú pháp là thông báo lỗi. Bạn muốn thông báo lỗi hướng người dùng đến một dòng và cột và hy vọng con người cũng có thể hiểu được. Phải mất rất nhiều mã để làm điều đó đúng, và các trình tạo trình phân tích cú pháp tốt hơn như antlr có thể giúp bạn giải quyết vấn đề đó.

Tuy nhiên, thế hệ tự động chỉ có thể đưa bạn đến nay và hầu hết các trình biên dịch mã nguồn mở thương mại và tồn tại lâu dài đều kết thúc bằng cách viết các trình phân tích cú pháp của chúng. Tôi đoán nếu bạn cảm thấy thoải mái khi làm điều này, bạn sẽ không hỏi câu hỏi này, vì vậy tôi khuyên bạn nên đi với trình tạo trình phân tích cú pháp.


2
Cảm ơn bạn vì câu trả lời! Tại sao việc xây dựng các thông báo lỗi có thể đọc được dễ dàng hơn bằng Trình tạo phân tích cú pháp hơn là Trình kết hợp phân tích cú pháp? (Bất kể những gì thực hiện chúng ta đang nói về, cụ thể) Ví dụ, tôi biết rằng cả hai Parsec và Thần chứa chức năng in thông báo lỗi bao gồm dòng + cột thông tin, vì vậy có vẻ như chắc chắn có thể làm điều này trong Parser combinators là tốt.
Qqwy

Không phải là bạn không thể in thông báo lỗi bằng bộ kết hợp trình phân tích cú pháp, đó là ưu điểm của chúng ít rõ ràng hơn khi bạn ném thông báo lỗi vào hỗn hợp. Thực hiện một ngữ pháp tương đối phức tạp bằng cả hai phương pháp và bạn sẽ thấy ý tôi là gì.
Karl Bielefeldt

Với Bộ kết hợp phân tích cú pháp, theo định nghĩa, tất cả những gì bạn có thể gặp phải trong tình trạng lỗi là "Bắt đầu từ thời điểm này, không tìm thấy đầu vào hợp pháp nào". Điều này không thực sự cho bạn biết những gì đã sai. Về lý thuyết, các trình phân tích cú pháp riêng lẻ được gọi vào thời điểm đó có thể cho bạn biết những gì nó mong đợi và KHÔNG tìm thấy, nhưng tất cả những gì bạn có thể làm là in tất cả những điều đó ra, tạo ra một thông báo lỗi looooooong.
John R. Strohm

1
Thành thật mà nói, các trình tạo phân tích cú pháp không biết chính xác các thông báo lỗi tốt của họ.
Miles Rout

Không phải mặc định, không, nhưng họ có các móc thuận tiện hơn để thêm các thông báo lỗi tốt.
Karl Bielefeldt

4

Sam Harwell, một trong những người duy trì trình tạo trình phân tích cú pháp ANTLR, gần đây đã viết :

Tôi thấy [tổ hợp] không đáp ứng nhu cầu của tôi:

  1. ANTLR cung cấp cho tôi các công cụ để quản lý những thứ như sự mơ hồ. Trong quá trình phát triển, có những công cụ có thể cho tôi thấy kết quả phân tích mơ hồ để tôi có thể loại bỏ những sự mơ hồ trong ngữ pháp. Trong thời gian chạy, tôi có thể tận dụng sự mơ hồ do kết quả đầu vào không hoàn chỉnh trong IDE để tạo ra kết quả chính xác hơn trong các tính năng như hoàn thành mã.
  2. Trong thực tế tôi đã thấy rằng các trình kết hợp trình phân tích cú pháp không phù hợp để đáp ứng các mục tiêu hiệu suất của tôi. Một phần của điều này quay trở lại
  3. Khi kết quả phân tích cú pháp được sử dụng cho các tính năng như phác thảo, hoàn thành mã và thụt lề thông minh, thật dễ dàng để các thay đổi tinh tế đối với ngữ pháp ảnh hưởng đến độ chính xác của các kết quả đó. ANTLR cung cấp các công cụ có thể biến các lỗi không khớp này thành các lỗi biên dịch, ngay cả trong trường hợp các kiểu sẽ biên dịch theo cách khác. Tôi có thể tự tin với nguyên mẫu một tính năng ngôn ngữ mới có ảnh hưởng đến ngữ pháp khi biết rằng tất cả các mã bổ sung hình thành IDE sẽ cung cấp trải nghiệm hoàn chỉnh cho tính năng mới ngay từ đầu. Cái ngã ba ANTLR 4 của tôi (mà mục tiêu C # dựa trên) là công cụ duy nhất tôi biết mà thậm chí còn cố gắng cung cấp tính năng này.

Về cơ bản, bộ kết hợp trình phân tích cú pháp là một đồ chơi thú vị để chơi, nhưng đơn giản là chúng không bị cắt ra để làm công việc nghiêm túc.


3

Như Karl đề cập, các trình tạo phân tích cú pháp có xu hướng báo cáo lỗi tốt hơn. Ngoài ra:

  • chúng có xu hướng nhanh hơn, vì mã được tạo có thể được chuyên dùng cho cú pháp và tạo các bảng nhảy cho giao diện.
  • họ có xu hướng có công cụ tốt hơn, để xác định cú pháp mơ hồ, loại bỏ đệ quy trái, điền vào các nhánh lỗi, v.v.
  • họ có xu hướng xử lý các định nghĩa đệ quy tốt hơn.
  • chúng có xu hướng mạnh mẽ hơn, vì các máy phát điện đã hoạt động lâu hơn và làm nhiều hơn cho nồi hơi cho bạn, làm giảm cơ hội bạn làm hỏng nó.

Mặt khác, tổ hợp 'có lợi thế riêng của họ:

  • chúng nằm trong mã, vì vậy nếu cú ​​pháp của bạn thay đổi trong thời gian chạy, bạn có thể dễ dàng thay đổi mọi thứ hơn.
  • chúng có xu hướng dễ ràng buộc hơn và thực sự tiêu thụ (đầu ra của trình tạo phân tích cú pháp có xu hướng rất chung chung và khó sử dụng).
  • chúng nằm trong mã, do đó có xu hướng dễ gỡ lỗi hơn một chút khi ngữ pháp của bạn không làm như bạn mong đợi.
  • họ có xu hướng có một đường cong học tập nông hơn vì chúng hoạt động như bất kỳ mã nào khác. Trình tạo phân tích cú pháp có xu hướng có những yêu cầu riêng để học cách làm cho công cụ hoạt động.

Các trình tạo phân tích cú pháp có xu hướng báo cáo lỗi khủng khiếp liên quan đến các trình phân tích cú pháp gốc đệ quy LL viết tay được sử dụng trong thế giới thực. Trình tạo phân tích cú pháp hiếm khi cung cấp các móc chuyển tiếp bảng trạng thái cần thiết để thêm chẩn đoán tuyệt vời. Đây là lý do tại sao gần như mọi trình biên dịch thực không sử dụng trình kết hợp trình phân tích cú pháp hoặc trình tạo trình phân tích cú pháp. Các trình phân tích cú pháp đệ quy LL rất đơn giản để xây dựng, mặc dù không phải là PC / PG "sạch", nhưng chúng hữu ích hơn.
dhchdhd
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.