Tại sao lại là đệ quy xấu?


20

Trong thiết kế trình biên dịch, tại sao phải loại bỏ đệ quy trong ngữ pháp? Tôi đang đọc rằng đó là bởi vì nó có thể gây ra một đệ quy vô hạn, nhưng nó có đúng với một ngữ pháp đệ quy đúng không?


2
Thông thường, trình biên dịch sử dụng phân tích cú pháp từ trên xuống. Nếu bạn có đệ quy trái, thì trình phân tích cú pháp sẽ đi vào đệ quy vô hạn. Tuy nhiên, trong đệ quy đúng, trình phân tích cú pháp có thể thấy tiền tố của chuỗi mà nó có cho đến nay. Do đó, nó có thể kiểm tra xem việc phái sinh đã đi "quá xa". Tất nhiên, bạn có thể hoán đổi vai trò và diễn giải các biểu thức từ bên phải, làm cho đệ quy bên phải trở nên xấu và đệ quy bên trái tốt.
Shaull

6
Đệ quy trái là xấu vì ngày xưa khi máy tính có RAM 16 KB, trình tạo trình phân tích cú pháp được sử dụng phổ biến nhất không thể đối phó với nó.
Andrej Bauer

Câu trả lời:


15

Ngữ pháp đệ quy còn lại không nhất thiết là một điều xấu. Các ngữ pháp này dễ dàng được phân tích cú pháp bằng cách sử dụng ngăn xếp để theo dõi các cụm từ đã được phân tích cú pháp, vì đó là trường hợp trong trình phân tích cú pháp LR .

Nhớ lại rằng một quy tắc đệ quy trái của một CF ngữ pháp có dạng:G=(V,Σ,R,S)

ααβ

αVβVΣ(V,Σ,R,S)

βαα

Bất cứ khi nào một thiết bị đầu cuối mới được nhận bởi trình phân tích ngữ pháp (từ lexer), thiết bị đầu cuối này được đẩy lên trên ngăn xếp: thao tác này được gọi là dịch chuyển .

Mỗi khi phía bên phải của quy tắc được khớp bởi một nhóm các phần tử liên tiếp ở đầu ngăn xếp, nhóm này được thay thế bằng một phần tử duy nhất đại diện cho cụm từ mới được khớp. Sự thay thế này được gọi là giảm .

Với các ngữ pháp đệ quy đúng, ngăn xếp có thể phát triển vô hạn cho đến khi giảm bớt, do đó hạn chế đáng kể các khả năng phân tích cú pháp. Tuy nhiên, những cái đệ quy còn lại sẽ cho phép trình biên dịch tạo ra các mức giảm sớm hơn (thực tế là càng sớm càng tốt). Xem mục wikipedia để biết thêm thông tin.


Nó sẽ giúp nếu bạn xác định các biến của bạn.
Andrew S

12

Hãy xem xét quy tắc này:

example : 'a' | example 'b' ;

Bây giờ hãy xem xét một trình phân tích cú pháp LL đang cố gắng khớp một chuỗi không khớp như 'b'quy tắc này. Vì 'a'không khớp, nên nó sẽ cố khớp example 'b'. Nhưng để làm như vậy, nó phải phù hợp example... đó là những gì nó đã cố gắng làm ở nơi đầu tiên. Nó có thể bị kẹt khi cố gắng mãi mãi để xem liệu nó có thể khớp hay không, bởi vì nó luôn cố gắng khớp cùng một luồng mã thông báo theo cùng một quy tắc.

Để ngăn chặn điều đó, bạn sẽ phải phân tích cú pháp từ bên phải (điều này khá hiếm, theo như tôi đã thấy, và sẽ thực hiện đệ quy đúng vấn đề), hạn chế một cách giả tạo số lượng lồng cho phép hoặc khớp mã thông báo trước khi đệ quy bắt đầu để luôn có trường hợp cơ bản (cụ thể là, nơi tất cả các mã thông báo đã được sử dụng và vẫn không có kết quả khớp hoàn chỉnh). Vì quy tắc đệ quy đúng đã thực hiện quy tắc thứ ba, nên nó không có cùng một vấn đề.


3
Bạn đang giả định một cách mù quáng rằng phân tích cú pháp là nhất thiết phải phân tích cú pháp từ trên xuống.
Revierpost

Tôi nhấn mạnh một cạm bẫy của một phương pháp phân tích cú pháp khá phổ biến - một vấn đề có thể dễ dàng tránh được. Chắc chắn có thể xử lý đệ quy trái, nhưng việc giữ lại nó tạo ra một giới hạn gần như luôn luôn không cần thiết đối với loại trình phân tích cú pháp có thể sử dụng nó.
cHao

Vâng, đó là một cách xây dựng và hữu ích hơn để đặt nó.
rebierpost

4

(Bây giờ tôi biết câu hỏi này khá cũ, nhưng trong trường hợp những người khác có cùng câu hỏi ...)

Bạn đang hỏi trong bối cảnh của trình phân tích cú pháp gốc đệ quy? Ví dụ, đối với ngữ pháp expr:: = expr + term | term, tại sao một cái gì đó như thế này (đệ quy trái):

// expr:: = expr + term
expr() {
   expr();
   if (token == '+') {
      getNextToken();
   }
   term();
}

là có vấn đề, nhưng không phải điều này (phải đệ quy)?

// expr:: = term + expr
expr() {
   term();
   if (token == '+') {
      getNextToken();
      expr();
   }
}

Có vẻ như cả hai phiên bản của expr()cuộc gọi mình. Nhưng sự khác biệt quan trọng là bối cảnh - tức là mã thông báo hiện tại khi cuộc gọi đệ quy đó được thực hiện.

Trong trường hợp đệ quy bên trái, expr()liên tục gọi chính nó với cùng một mã thông báo và không có tiến triển nào được thực hiện. Trong trường hợp đệ quy đúng, nó tiêu thụ một số đầu vào trong lệnh gọi term()và mã thông báo PLUS trước khi thực hiện cuộc gọi đến expr(). Vì vậy, tại thời điểm này, cuộc gọi đệ quy có thể gọi hạn và sau đó kết thúc trước khi đạt lại kiểm tra if.

Ví dụ: hãy xem xét phân tích cú pháp 2 + 3 + 4. Trình phân tích cú pháp đệ quy bên trái gọi expr()vô hạn trong khi bị mắc kẹt trên mã thông báo đầu tiên, trong khi trình phân tích đệ quy bên phải tiêu thụ "2 +" trước khi gọi expr()lại. Cuộc gọi thứ hai expr()khớp với "3 +" và các cuộc gọi expr()chỉ còn 4 cuộc gọi . Cả 4 khớp với một thuật ngữ và phân tích cú pháp chấm dứt mà không có thêm bất kỳ cuộc gọi nào expr().


2

Từ hướng dẫn sử dụng Bison:

"Bất kỳ loại trình tự nào cũng có thể được xác định bằng cách sử dụng đệ quy bên trái hoặc đệ quy bên phải, nhưng bạn phải luôn luôn sử dụng đệ quy bên trái , bởi vì nó có thể phân tích một chuỗi bất kỳ số phần tử nào có không gian ngăn xếp giới hạn. Đệ quy bên phải sử dụng không gian trên ngăn xếp Bison trong tỷ lệ với số phần tử trong chuỗi, bởi vì tất cả các phần tử phải được chuyển lên ngăn xếp trước khi quy tắc có thể được áp dụng ngay cả một lần. Xem Thuật toán Bison Parser, để giải thích thêm về điều này. "

http://www.gnu.org/software/bison/manual/html_node/Recursion.html

Vì vậy, nó phụ thuộc vào thuật toán của trình phân tích cú pháp, nhưng như đã nêu trong các câu trả lời khác, một số trình phân tích cú pháp có thể đơn giản không hoạt động với đệ quy trái

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.