Làm thế nào tôi nên chỉ định một ngữ pháp cho một trình phân tích cú pháp?


12

Tôi đã lập trình được nhiều năm, nhưng một nhiệm vụ vẫn khiến tôi mất nhiều thời gian là chỉ định một ngữ pháp cho trình phân tích cú pháp và thậm chí sau nỗ lực quá mức này, tôi không bao giờ chắc chắn rằng ngữ pháp mà tôi nghĩ ra là tốt ( bằng bất kỳ biện pháp hợp lý nào của "tốt").

Tôi không hy vọng rằng có một thuật toán để tự động hóa quá trình chỉ định ngữ pháp, nhưng tôi hy vọng rằng có nhiều cách để cấu trúc vấn đề loại bỏ phần lớn phỏng đoán và thử và sai của phương pháp hiện tại của tôi.

Suy nghĩ đầu tiên của tôi là đọc về các trình phân tích cú pháp và tôi đã thực hiện một số điều này, nhưng tất cả mọi thứ tôi đã đọc về chủ đề này đều lấy ngữ pháp như một (hoặc đủ tầm thường để người ta có thể chỉ định nó bằng cách kiểm tra), và tập trung vào vấn đề dịch ngữ pháp này thành một trình phân tích cú pháp. Tôi quan tâm đến vấn đề ngay trước đây: làm thế nào để xác định ngữ pháp ở vị trí đầu tiên.

Tôi chủ yếu quan tâm đến vấn đề chỉ định một ngữ pháp chính thức đại diện cho một tập hợp các ví dụ cụ thể (tích cực và tiêu cực). Điều này khác với vấn đề thiết kế một cú pháp mới . Cảm ơn Macneil đã chỉ ra sự khác biệt này.

Tôi chưa bao giờ thực sự đánh giá cao sự khác biệt giữa ngữ pháp và cú pháp, nhưng bây giờ khi tôi bắt đầu thấy nó, tôi có thể làm rõ sự làm rõ đầu tiên của mình bằng cách nói rằng tôi chủ yếu quan tâm đến vấn đề chỉ định một ngữ pháp sẽ thi hành một cú pháp được xác định trước: thực tế là trong trường hợp của tôi, cơ sở cho cú pháp này thường là một tập hợp các ví dụ tích cực và tiêu cực.

Làm thế nào là ngữ pháp được chỉ định cho một trình phân tích cú pháp? Có một cuốn sách hoặc tài liệu tham khảo nào ngoài đó là tiêu chuẩn thực tế để mô tả các thực tiễn tốt nhất, phương pháp thiết kế và thông tin hữu ích khác về việc chỉ định ngữ pháp cho trình phân tích cú pháp không? Những điểm nào, khi đọc về ngữ pháp trình phân tích cú pháp, tôi nên tập trung vào?


1
Tôi đã chỉnh sửa câu hỏi của bạn một chút để tập trung vào vấn đề thực tế của bạn. Trang web này chính xác là nơi bạn có thể đặt câu hỏi về ngữ pháp và trình phân tích cú pháp và nhận câu trả lời của chuyên gia. Nếu có những nguồn lực bên ngoài đáng để xem xét, chúng sẽ xuất hiện một cách tự nhiên trong các câu trả lời trực tiếp giúp bạn.
Adam Lear

8
@kjo Thật không may. Nếu tất cả những gì bạn yêu cầu là một danh sách các tài liệu tham khảo, thì bạn không sử dụng Stack Exchange cho toàn bộ tiềm năng của nó. Vấn đề meta của bạn không sử dụng trang web như dự định. Danh sách các câu hỏi hầu như không được khuyến khích trên Stack Exchange vì chúng không phù hợp với mô hình Hỏi và Đáp. Tôi đặc biệt khuyên bạn nên thay đổi suy nghĩ của mình theo hướng đặt câu hỏi có câu trả lời, không phải mục, ý tưởng hoặc ý kiến .
Adam Lear

3
@kjo Đó chắc chắn là một câu hỏi, nhưng không phải là câu hỏi đúng để hỏi về Stack Exchange . SE không ở đây để xây dựng danh sách các tài liệu tham khảo. Đó là vào đây để được tham chiếu. Xin vui lòng đọc qua bài viết meta tôi liên kết đến trong bình luận của tôi để được giải thích chi tiết hơn.
Adam Lear

5
@kjo: Xin đừng nản lòng! Các chỉnh sửa của Anna giữ cốt lõi và cốt lõi của câu hỏi của bạn và cô ấy đã giúp bạn bằng cách làm cho câu hỏi của bạn giống với hình thức mà chúng tôi mong đợi trên Lập trình viên. Tôi biết không có tài liệu tham khảo chính xác mà bạn tìm kiếm, nhưng đã có thể cung cấp một câu trả lời. [OTOH, nếu tôi biết một tài liệu tham khảo như vậy, tôi chắc chắn sẽ đưa nó vào.] Chúng tôi muốn khuyến khích nhiều câu trả lời như của tôi bởi vì, trong trường hợp cụ thể này, tôi không tin có một tài liệu tham khảo cho những gì bạn tìm kiếm, chỉ kinh nghiệm từ việc nói chuyện với người khác.
Macneil

4
@kjo Tôi đã quay lại các chỉnh sửa của Anna và cố gắng kết hợp một lời kêu gọi cụ thể để tham khảo chính tắc dựa trên hướng dẫn của chúng tôi về các đề xuất sách : có rất nhiều thông tin tốt trong các câu trả lời được cung cấp và để vô hiệu hóa chúng bằng cách đưa ra phạm vi của câu hỏi chỉ là về việc tìm kiếm một cuốn sách sẽ là một sự lãng phí. Bây giờ nếu tất cả chúng ta có thể dừng lại với chỉnh sửa cảnh báo, điều đó thật tuyệt vời.

Câu trả lời:


12

Từ các tệp mẫu, bạn sẽ cần đưa ra quyết định dựa trên mức độ bạn muốn khái quát từ các ví dụ đó. Giả sử bạn có ba mẫu sau: (mỗi mẫu là một tệp riêng)

f() {}
f(a,b) {b+a}
int x = 5;

Bạn có thể chỉ định tầm thường hai ngữ pháp sẽ chấp nhận các mẫu này:

Ngữ pháp tầm thường Một:

start ::= f() {} | f(a,b) {b+a} | int x = 5;

Ngữ pháp tầm thường Hai:

start ::= tokens
tokens ::= token tokens | <empty>
token ::= identifier | literal | { | } | ( | ) | , | + | = | ;

Mẫu đầu tiên là tầm thường vì nó chỉ chấp nhận ba mẫu. Cái thứ hai là tầm thường bởi vì nó chấp nhận mọi thứ có thể sử dụng các loại mã thông báo đó. [Đối với cuộc thảo luận này, tôi sẽ giả định rằng bạn không quan tâm nhiều đến thiết kế mã thông báo: Thật đơn giản để giả định số nhận dạng, số và dấu câu làm mã thông báo của bạn và bạn có thể mượn bất kỳ mã thông báo nào được đặt từ bất kỳ ngôn ngữ kịch bản nào như thế nào cũng được.]

Vì vậy, quy trình bạn cần tuân theo là bắt đầu ở cấp độ cao và quyết định "tôi muốn cho phép bao nhiêu trường hợp?" Nếu một cấu trúc cú pháp có thể có ý nghĩa để lặp lại bất kỳ số lần nào, chẳng hạn như methods trong một lớp, bạn sẽ muốn một quy tắc với hình thức này:

methods ::= method methods | empty

Điều này được nêu rõ hơn trong EBNF là:

methods ::= {method}

Có thể sẽ rõ ràng khi bạn chỉ muốn không hoặc một trường hợp (có nghĩa là cấu trúc là tùy chọn, như với extendsmệnh đề cho một lớp Java) hoặc khi bạn muốn cho phép một hoặc nhiều phiên bản (như với một trình khởi tạo biến trong khai báo ). Bạn sẽ cần chú ý đến các vấn đề như yêu cầu dấu phân cách giữa các phần tử (như ,trong danh sách đối số), yêu cầu dấu kết thúc sau mỗi phần tử (như với các ;câu lệnh riêng biệt) hoặc không yêu cầu dấu phân cách hoặc dấu kết thúc (như trường hợp với các phương thức trong một lớp).

Nếu ngôn ngữ của bạn sử dụng các biểu thức số học, bạn sẽ dễ dàng sao chép từ các quy tắc ưu tiên của ngôn ngữ hiện có. Tốt nhất là bám vào một cái gì đó nổi tiếng, như quy tắc biểu thức của C, hơn là tìm kiếm thứ gì đó kỳ lạ, nhưng chỉ với điều kiện là tất cả những thứ khác đều bằng nhau.

Ngoài các vấn đề ưu tiên (những gì được phân tích cú pháp với nhau) và các vấn đề lặp lại (bao nhiêu phần tử sẽ xảy ra, chúng được phân tách như thế nào?), Bạn cũng sẽ cần phải suy nghĩ về thứ tự: Phải luôn luôn xuất hiện trước một điều khác? Nếu một điều được bao gồm, có nên loại trừ một điều khác?

Tại thời điểm này, bạn có thể bị ép buộc thực thi về mặt ngữ pháp một số quy tắc, một quy tắc như nếu Persontuổi của bạn được chỉ định, bạn cũng không muốn cho phép ngày sinh của họ được chỉ định. Mặc dù bạn có thể xây dựng ngữ pháp của mình để làm như vậy, nhưng bạn có thể thấy việc thực thi điều này dễ dàng hơn với "kiểm tra ngữ nghĩa" sau khi mọi thứ được phân tích cú pháp. Điều này giữ cho ngữ pháp đơn giản hơn và, theo tôi, làm cho các thông báo lỗi tốt hơn khi quy tắc bị vi phạm.


1
+1 cho các thông báo lỗi tốt hơn. Hầu hết người dùng ngôn ngữ của bạn sẽ không phải là chuyên gia, cho dù có 10 hoặc 10 triệu người trong số họ. Lý thuyết phân tích đã bỏ qua khía cạnh này quá lâu.
MSalters

10

Tôi có thể học cách chỉ định ngữ pháp cho trình phân tích cú pháp ở đâu?

Đối với hầu hết máy phát điện phân tích cú pháp, nó thường là một số biến thể của Backus-Naur 's <nonterminal> ::= expressionđịnh dạng. Tôi sẽ tiếp tục giả định rằng bạn đang sử dụng một cái gì đó tương tự và không cố gắng xây dựng các trình phân tích cú pháp của bạn bằng tay. Nếu bạn có thể tạo một trình phân tích cú pháp cho định dạng mà bạn đã được cung cấp cú pháp (Tôi đã bao gồm một vấn đề mẫu bên dưới), việc chỉ định ngữ pháp không phải là vấn đề của bạn.

Những gì tôi nghĩ rằng bạn đang chống lại là cú pháp bói toán từ một tập hợp các mẫu, đây thực sự là nhận dạng mẫu nhiều hơn so với phân tích cú pháp. Nếu bạn phải dùng đến điều đó, điều đó có nghĩa là bất cứ ai đang cung cấp dữ liệu của bạn đều không thể cung cấp cho bạn cú pháp của nó vì họ không xử lý tốt định dạng của nó. Nếu bạn có tùy chọn đẩy lùi và bảo họ đưa ra định nghĩa chính thức, hãy làm điều đó. Sẽ không công bằng khi họ đưa ra cho bạn một vấn đề mơ hồ nếu bạn có thể chịu trách nhiệm về hậu quả của trình phân tích cú pháp dựa trên cú pháp suy diễn chấp nhận đầu vào xấu hoặc từ chối đầu vào tốt.

... Tôi không bao giờ chắc chắn rằng ngữ pháp tôi đã đưa ra là tốt (bằng bất kỳ biện pháp hợp lý nào là "tốt").

"Tốt" trong tình huống của bạn sẽ có nghĩa là "phân tích các mặt tích cực và loại bỏ các tiêu cực." Không có bất kỳ đặc điểm chính thức nào khác về cú pháp của tệp đầu vào của bạn, các mẫu là trường hợp thử nghiệm duy nhất của bạn và bạn không thể làm gì tốt hơn thế. Bạn có thể đặt chân xuống và nói rằng chỉ những ví dụ tích cực là tốt và từ chối bất cứ điều gì khác, nhưng điều đó có lẽ không theo tinh thần của những gì bạn đang cố gắng thực hiện.

Trong các trường hợp saner, kiểm tra một ngữ pháp cũng giống như kiểm tra bất cứ điều gì khác: bạn phải đưa ra đủ các trường hợp kiểm tra để thực hiện tất cả các biến thể của nonterminals (và các thiết bị đầu cuối, nếu chúng được tạo bởi một từ vựng).


Bài toán mẫu

Viết một ngữ pháp sẽ phân tích các tệp văn bản có chứa một danh sách theo quy định dưới đây:

  • Một danh sách bao gồm không hoặc nhiều thứ .
  • Một thứ bao gồm một định danh , một dấu ngoặc mở, một danh sách vật phẩm và một dấu ngoặc nhọn.
  • Một _item_list_ bao gồm 0 hoặc nhiều mục .
  • Một mục cấu thành của một định danh , một dấu bằng, một định danh khác và một dấu chấm phẩy.
  • Một định danh là một chuỗi một hoặc nhiều ký tự AZ, az, 0-9 hoặc dấu gạch dưới.
  • Khoảng trắng được bỏ qua.

Ví dụ về đầu vào (tất cả hợp lệ):

clank { foo = bar; baz = bear; }
clunk {
    quux =bletch;
    281_apple = OU812;
    He_Eats=Asparagus ; }

2
Và đảm bảo sử dụng "một số biến thể của Backus-Naur" chứ không phải bản thân BNF. BNF có thể diễn đạt một ngữ pháp, nhưng nó tạo ra rất nhiều khái niệm rất phổ biến, chẳng hạn như danh sách, phức tạp hơn nhiều so với mức cần thiết. Có nhiều phiên bản cải tiến khác nhau, chẳng hạn như EBNF, cải thiện những vấn đề này.
Mason Wheeler

7

Câu trả lời của Macneil và Blrfl là tuyệt vời. Tôi chỉ muốn thêm một số ý kiến ​​về việc bắt đầu quá trình.

Một cú pháp chỉ là một cách để đại diện cho một chương trình . Vì vậy, cú pháp ngôn ngữ của bạn nên được xác định bằng câu trả lời của bạn cho câu hỏi này: Chương trình là gì?

Bạn có thể nói rằng một chương trình là một tập hợp các lớp. Được rồi, điều đó cho chúng ta

program ::= class*

như một điểm khởi đầu. Hoặc bạn có thể phải viết nó

program ::= ε
         |  class program

Bây giờ, một lớp học là gì? Nó có một cái tên; một đặc điểm kỹ thuật siêu lớp tùy chọn; và một loạt các hàm tạo, phương thức và khai báo trường. Ngoài ra, bạn cần một số cách để nhóm một lớp thành một đơn vị (không rõ ràng) và điều đó sẽ liên quan đến một số nhượng bộ về khả năng sử dụng (ví dụ: gắn thẻ nó với từ dành riêng class). Được chứ:

class ::= "class" identifier extends-clause? "{" class-member-decl * "}"

Đó là một ký hiệu ("cú pháp cụ thể") bạn có thể chọn. Hoặc bạn có thể dễ dàng quyết định điều này:

class ::= "(" "class" identifier extends-clause "(" class-member-decl* ")" ")"

hoặc là

class ::= "class" identifier "=" "CLASS" extends-clause? class-member-decl* "END"

Có lẽ bạn đã hoàn toàn đưa ra quyết định này, đặc biệt nếu bạn có ví dụ, nhưng tôi chỉ muốn củng cố quan điểm: Cấu trúc của cú pháp được xác định bởi cấu trúc của các chương trình mà nó đại diện. Đó là những gì giúp bạn vượt qua "ngữ pháp tầm thường" từ câu trả lời của Macneil. Các chương trình ví dụ vẫn rất quan trọng, mặc dù. Họ phục vụ hai mục đích. Đầu tiên, họ giúp bạn tìm ra, ở cấp độ trừu tượng, chương trình là gì. Thứ hai, chúng giúp bạn quyết định cú pháp cụ thể nào bạn nên sử dụng để thể hiện cấu trúc ngôn ngữ của bạn.

Khi bạn đã hạ cấu trúc, bạn nên quay lại và xử lý các vấn đề như cho phép khoảng trắng và nhận xét, khắc phục sự mơ hồ, v.v ... Đây là những điều quan trọng, nhưng chúng là thứ yếu trong thiết kế tổng thể, và chúng phụ thuộc rất nhiều vào công nghệ phân tích cú pháp bạn đang sử dụng.

Cuối cùng, đừng cố gắng trình bày mọi thứ về ngôn ngữ của bạn trong ngữ pháp. Ví dụ: bạn có thể muốn cấm một số loại mã không thể truy cập (ví dụ: một câu lệnh sau một return, như trong Java). Có lẽ bạn không nên cố gắng nhồi nhét nó vào ngữ pháp, bởi vì bạn sẽ bỏ lỡ mọi thứ (rất tiếc, nếu returnlà trong niềng răng, hoặc nếu cả hai nhánh của ifcâu lệnh trở lại thì sao?) Hoặc bạn sẽ làm cho ngữ pháp của bạn quá phức tạp để quản lý. Đó là một hạn chế nhạy cảm theo ngữ cảnh ; viết nó như một đường chuyền riêng Một ví dụ rất phổ biến khác về ràng buộc theo ngữ cảnh là một hệ thống kiểu. Bạn có thể từ chối các biểu thức như 1 + "a"trong ngữ pháp, nếu bạn đã làm việc đủ chăm chỉ, nhưng bạn không thể từ chối 1 + x(nơi xcó chuỗi kiểu). Vì thếtránh các hạn chế nửa nướng trong ngữ pháp và thực hiện chúng một cách chính xác như một cách vượt qua riêng biệt.

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.