Phân tích cú pháp ngữ pháp không có ngữ cảnh tùy ý, chủ yếu là các đoạn ngắn


20

Tôi muốn phân tích các ngôn ngữ cụ thể miền do người dùng xác định. Các ngôn ngữ này thường gần với các ký hiệu toán học (tôi không phân tích ngôn ngữ tự nhiên). Người dùng xác định DSL của họ theo ký hiệu BNF, như thế này:

expr ::= LiteralInteger
       | ( expr )
       | expr + expr
       | expr * expr

Thích đầu vào 1 + ( 2 * 3 )phải được chấp nhận, trong khi đầu vào như 1 +phải bị từ chối là không chính xác và đầu vào như 1 + 2 * 3phải bị từ chối là mơ hồ.

Một khó khăn trung tâm ở đây là đối phó với các ngữ pháp mơ hồ theo cách thân thiện với người dùng. Hạn chế ngữ pháp không rõ ràng không phải là một lựa chọn: đó là cách ngôn ngữ - ý tưởng là các nhà văn thích bỏ qua dấu ngoặc đơn khi không cần thiết để tránh sự mơ hồ. Miễn là một biểu thức không mơ hồ, tôi cần phân tích nó và nếu không, tôi cần phải từ chối nó.

Trình phân tích cú pháp của tôi phải hoạt động trên mọi ngữ pháp không ngữ cảnh, ngay cả những từ mơ hồ và phải chấp nhận tất cả các đầu vào không rõ ràng. Tôi cần cây phân tích cho tất cả các đầu vào được chấp nhận. Đối với đầu vào không hợp lệ hoặc mơ hồ, lý tưởng nhất là tôi muốn có thông báo lỗi tốt, nhưng để bắt đầu với tôi sẽ lấy những gì tôi có thể nhận được.

Tôi thường sẽ gọi trình phân tích cú pháp trên các đầu vào tương đối ngắn, với đầu vào dài hơn thỉnh thoảng. Vì vậy, thuật toán nhanh hơn tiệm cận có thể không phải là sự lựa chọn tốt nhất. Tôi muốn tối ưu hóa để phân phối khoảng 80% đầu vào dài dưới 20 ký hiệu, 19% từ 20 đến 50 ký hiệu và 1% đầu vào hiếm hơn. Tốc độ cho đầu vào không hợp lệ không phải là một mối quan tâm chính. Hơn nữa, tôi mong đợi một sửa đổi của DSL xung quanh mỗi 1000 đến 100000 đầu vào; Tôi có thể dành vài giây để xử lý ngữ pháp của mình chứ không phải vài phút.

Tôi nên điều tra (các) thuật toán phân tích cú pháp nào, với các kích thước đầu vào điển hình của tôi? Báo cáo lỗi có nên là một yếu tố trong lựa chọn của tôi hay tôi nên tập trung vào phân tích cú pháp đầu vào rõ ràng và có thể chạy một trình phân tích cú pháp chậm hoàn toàn riêng biệt để cung cấp phản hồi lỗi?

(Trong dự án mà tôi cần (một thời gian trước), tôi đã sử dụng CYK , điều này không quá khó để thực hiện và hoạt động đầy đủ cho kích thước đầu vào của tôi nhưng không tạo ra các lỗi rất hay.)


Đặc biệt báo cáo lỗi tốt dường như khó đạt được. Bạn có thể có nhiều thay đổi cục bộ dẫn đến đầu vào được chấp nhận trong trường hợp ngữ pháp không rõ ràng.
Raphael

Tôi chỉ trả lời dưới đây. Có một chút lúng túng khi trả lời một sửa đổi của một câu hỏi cũ đã có một câu trả lời được đón nhận. Rõ ràng tôi không được phép trả lời theo cách tương tự, nhưng người dùng sẽ đọc cả hai câu trả lời như thể họ đang trả lời cùng một câu hỏi.
babou

Bạn có thực sự mong đợi một thông báo lỗi cho đầu vào mơ hồ, nếu người dùng viết x+y+z.
babou

@babou Tôi không thay đổi câu hỏi, tôi chỉ thêm các yêu cầu làm rõ trong các bình luận (hiện đã bị xóa). Đối với ngữ pháp nhỏ được đưa ra ở đây, tôi đã không chỉ định tính kết hợp cho +, do đó, x+y+zthực sự mơ hồ do đó sai lầm.
Gilles 'SO- ngừng trở nên xấu xa'

Vâng, đó là câu cuối cùng của bạn, chỉ cần thêm, ngay cả khi giữa các dấu ngoặc đơn. Bạn dường như nói: Cuối cùng tôi đã làm điều đó với CYK, nhưng nó không còn phù hợp vì một số lý do. Và tôi tự hỏi những lý do chính xác có thể là gì ... Bây giờ , bạn là người có nhiều kinh nghiệm nhất với loại vấn đề của bạn và giải pháp bạn sử dụng, vì vậy người ta sẽ mong đợi thêm thông tin từ bạn nếu có thêm câu trả lời.
babou

Câu trả lời:


19

Có lẽ thuật toán lý tưởng cho nhu cầu của bạn là phân tích LL tổng quát , hoặc GLL. Đây là một thuật toán rất mới (bài báo đã được xuất bản năm 2010). Theo một cách nào đó, đó là thuật toán Earley được tăng cường với một ngăn xếp có cấu trúc đồ thị (GSS) và sử dụng LL (1) lookahead.

Thuật toán này khá giống với LL (1) cũ, ngoại trừ việc nó không từ chối ngữ pháp nếu chúng không phải là LL (1): nó chỉ thử tất cả các phân tích LL (1) có thể. Nó sử dụng một biểu đồ có hướng cho mọi điểm trong phân tích cú pháp, điều đó có nghĩa là nếu gặp phải trạng thái phân tích cú pháp đã được xử lý trước đó, thì nó chỉ đơn giản là hợp nhất hai đỉnh này. Điều này làm cho nó phù hợp với các ngữ pháp đệ quy trái, không giống như LL. Để biết chi tiết chính xác về hoạt động bên trong của nó, hãy đọc tờ giấy (đây là một loại giấy khá dễ đọc, mặc dù súp nhãn đòi hỏi sự kiên trì).

Thuật toán có một số lợi thế rõ ràng liên quan đến nhu cầu của bạn so với các thuật toán phân tích cú pháp chung khác (mà tôi biết). Thứ nhất, việc thực hiện rất dễ dàng: tôi nghĩ chỉ Earley mới dễ thực hiện hơn. Thứ hai, hiệu suất khá tốt: trên thực tế, nó trở nên nhanh như LL (1) trên các ngữ pháp là LL (1). Thứ ba, phục hồi phân tích cú pháp là khá dễ dàng, và kiểm tra xem có nhiều hơn một phân tích có thể hay không.

Ưu điểm chính mà GLL có là dựa trên LL (1) và do đó rất dễ hiểu và gỡ lỗi, khi thực hiện, khi thiết kế ngữ pháp cũng như khi phân tích cú pháp đầu vào. Hơn nữa, nó cũng làm cho việc xử lý lỗi dễ dàng hơn: bạn biết chính xác nơi có thể phân tích cú pháp bị mắc kẹt và cách chúng có thể tiếp tục. Bạn có thể dễ dàng đưa ra các phân tích có thể có tại điểm xảy ra lỗi và giả sử, 3 điểm cuối cùng trong đó các phân tích bị mắc kẹt. Thay vào đó, bạn có thể chọn để cố gắng khắc phục lỗi và đánh dấu sản phẩm mà phép phân tích có độ phân giải cao nhất đang hoạt động là 'hoàn thành' cho phân tích đó và xem liệu phân tích cú pháp có thể tiếp tục sau đó hay không (giả sử ai đó đã quên dấu ngoặc đơn). Bạn thậm chí có thể làm điều đó, giả sử, 5 phân tích có mức độ xa nhất.

Nhược điểm duy nhất của thuật toán là nó mới, có nghĩa là không có triển khai được thiết lập tốt nào có sẵn. Đây có thể không phải là vấn đề với bạn - Tôi đã tự mình thực hiện thuật toán và việc này khá dễ thực hiện.


Rất vui được học một cái gì đó mới. Khi tôi cần điều này (một vài năm trước, trong một dự án mà tôi muốn hồi sinh một ngày nào đó), tôi đã sử dụng CYK, phần lớn vì đó là thuật toán đầu tiên tôi tìm thấy. Làm thế nào để GLL xử lý đầu vào mơ hồ? Bài báo dường như không thảo luận về điều này, nhưng tôi chỉ đọc lướt qua nó.
Gilles 'SO- ngừng trở thành ác quỷ'

@Gilles: nó xây dựng một ngăn xếp có cấu trúc biểu đồ và tất cả các phân tích (có khả năng theo cấp số nhân) được trình bày gọn trong biểu đồ này, tương tự như cách GLR hoạt động. Nếu tôi nhớ lại một cách chính xác, bài báo được đề cập trong cstheory.stackexchange.com/questions/7374/ đối phó với điều này.
Alex ten Brink

@Gilles Trình phân tích cú pháp 2010 này dường như phải được lập trình bằng tay từ ngữ pháp, không quá đầy đủ nếu bạn có một số ngôn ngữ hoặc nếu bạn thường sửa đổi ngôn ngữ. Kỹ thuật tạo tự động từ ngữ pháp của trình phân tích cú pháp chung theo bất kỳ chiến lược đã chọn nào (LL, LR hoặc các loại khác) và tạo ra một rừng gồm tất cả các phân tích đã được biết đến trong khoảng 40 năm. Tuy nhiên, có những vấn đề tiềm ẩn liên quan đến sự phức tạp và tổ chức của biểu đồ đại diện cho các phân tích cú pháp. Số lượng phân tích có thể tệ hơn số mũ: vô hạn. Khôi phục lỗi có thể sử dụng các kỹ thuật độc lập phân tích cú pháp có hệ thống hơn.
babou

GLL liên quan đến LL (*) được tìm thấy trong ANTLR như thế nào?
Raphael

6

Công ty của tôi (Semantic Design) đã sử dụng bộ phân tích GLR rất thành công để thực hiện chính xác những gì OP đề xuất trong việc phân tích cả hai ngôn ngữ cụ thể của miền và phân tích các ngôn ngữ lập trình "cổ điển", với Bộ công cụ tái cấu trúc phần mềm DMS của chúng tôi. Điều này hỗ trợ chuyển đổi chương trình nguồn-nguồn được sử dụng để tái cấu trúc chương trình quy mô lớn / kỹ thuật đảo ngược / tạo mã chuyển tiếp. Điều này bao gồm tự động sửa chữa các lỗi cú pháp một cách khá thực tế. Sử dụng GLR làm nền tảng và một số thay đổi khác (vị ngữ ngữ nghĩa, đầu vào được đặt mã thông báo thay vì chỉ nhập mã thông báo, ...) chúng tôi đã quản lý để xây dựng trình phân tích cú pháp cho khoảng 40 ngôn ngữ.

Quan trọng như khả năng phân tích các phiên bản ngôn ngữ đầy đủ, GLR cũng đã được chứng minh là cực kỳ hữu ích trong việc phân tích các quy tắc viết lại từ nguồn thành nguồn . Đây là những đoạn chương trình có ngữ cảnh ít hơn nhiều so với một chương trình đầy đủ, và do đó thường có nhiều sự mơ hồ. Chúng tôi sử dụng các chú thích đặc biệt (ví dụ: nhấn mạnh rằng một cụm từ tương ứng với một ngữ pháp cụ thể) để giúp giải quyết những sự mơ hồ đó trong / sau khi phân tích các quy tắc. Bằng cách tổ chức bộ máy phân tích cú pháp GLR và các công cụ xung quanh nó, chúng tôi có được trình phân tích cú pháp để viết lại quy tắc "miễn phí" khi chúng tôi có trình phân tích cú pháp cho ngôn ngữ của nó. Công cụ DMS có một trình bổ sung quy tắc viết lại tích hợp, sau đó có thể được sử dụng để áp dụng các quy tắc này để thực hiện các thay đổi mã mong muốn.

Có lẽ kết quả ngoạn mục nhất của chúng tôi là khả năng phân tích đầy đủ C ++ 14 , bất chấp mọi sự mơ hồ, sử dụng ngữ pháp không ngữ cảnh làm cơ sở. Tôi lưu ý rằng tất cả các trình biên dịch C ++ cổ điển (GCC, Clang) đã từ bỏ khả năng thực hiện việc này và sử dụng các trình phân tích cú pháp viết tay (IMHO khiến chúng khó bảo trì hơn nhiều, nhưng sau đó, chúng không phải là vấn đề của tôi). Chúng tôi đã sử dụng máy móc này để thực hiện các thay đổi lớn đối với kiến ​​trúc của các hệ thống C ++ lớn.

Hiệu suất khôn ngoan, trình phân tích cú pháp GLR của chúng tôi rất nhanh: hàng chục nghìn dòng mỗi giây. Điều này thấp hơn nhiều so với tình trạng của nghệ thuật, nhưng chúng tôi đã không thực hiện một nỗ lực nghiêm túc nào để tối ưu hóa điều này và một số điểm nghẽn nằm trong xử lý luồng ký tự (Unicode đầy đủ). Để xây dựng các trình phân tích cú pháp như vậy, chúng tôi xử lý trước các ngữ pháp miễn phí ngữ cảnh bằng cách sử dụng một cái gì đó khá gần với trình tạo bộ phân tích cú pháp LR (1); cái này thường chạy trên một máy trạm hiện đại trong mười giây với các ngữ pháp lớn có kích thước bằng C ++. Đáng ngạc nhiên, đối với các ngôn ngữ rất phức tạp như COBOL và C ++ hiện đại, việc tạo ra các từ vựng hóa ra mất khoảng một phút; một số DFA được định nghĩa qua Unicode có nhiều lông. Tôi chỉ thực hiện Ruby (với một chương trình con đầy đủ cho các biểu thức chính đáng kinh ngạc của nó) như một bài tập ngón tay; DMS có thể xử lý ngữ pháp và ngữ pháp của nó với nhau trong khoảng 8 giây.


@Raphael: Liên kết "thay đổi lớn" chỉ ra một tập hợp các tài liệu kỹ thuật theo phong cách học thuật, bao gồm một số ít về tái cấu trúc kiến ​​trúc C ++, một liên kết trên chính công cụ DMS (khá cũ nhưng mô tả cơ bản tốt) và một trên chủ đề kỳ lạ của việc nắm bắt và tái sử dụng thiết kế, đó là động lực ban đầu của DMS (vẫn chưa được giải quyết, thật không may, nhưng DMS dù sao cũng khá hữu ích).
Ira Baxter

1

Có nhiều trình phân tích cú pháp không ngữ cảnh chung có thể phân tích các câu mơ hồ (theo một ngữ pháp mơ hồ). Chúng có nhiều tên khác nhau, đáng chú ý là các trình phân tích cú pháp lập trình hoặc biểu đồ động. Công cụ được biết đến nhiều nhất và bên cạnh đơn giản nhất có lẽ là trình phân tích cú pháp CYK mà bạn đang sử dụng. Tính tổng quát đó là cần thiết vì bạn phải xử lý nhiều phân tích cú pháp và có thể không biết cho đến cuối cùng liệu bạn có đang xử lý một sự mơ hồ hay không.

Từ những gì bạn nói, tôi sẽ nghĩ rằng CYK không phải là một lựa chọn tồi. Bạn có thể không có nhiều để đạt được bằng cách thêm dự đoán (LL hoặc LR) và nó thực sự có thể có chi phí bằng cách phân biệt các tính toán nên được hợp nhất thay vì phân biệt đối xử (đặc biệt là trong trường hợp LR). Họ cũng có thể có một chi phí tương ứng trong kích thước của rừng phân tích được tạo ra (có thể có vai trò trong các lỗi mơ hồ). Trên thực tế, trong khi tôi không chắc chắn làm thế nào để so sánh chính thức tính đầy đủ của các thuật toán tinh vi hơn, tôi biết rằng CYK không chia sẻ tính toán tốt.

Bây giờ, tôi không tin rằng có nhiều tài liệu về các trình phân tích cú pháp CF chung cho các ngữ pháp mơ hồ chỉ nên chấp nhận đầu vào không rõ ràng. Tôi không nhớ là đã thấy bất kỳ, có lẽ bởi vì ngay cả đối với các tài liệu kỹ thuật, hoặc thậm chí các ngôn ngữ lập trình, sự mơ hồ cú pháp vẫn được chấp nhận miễn là nó có thể được giải quyết bằng các phương tiện khác (ví dụ: sự mơ hồ trong các biểu thức ADA).

Tôi thực sự tự hỏi tại sao bạn muốn thay đổi thuật toán của bạn, thay vì bám vào những gì bạn có. Điều đó có thể giúp tôi hiểu loại thay đổi nào có thể giúp bạn tốt nhất. Đây có phải là một vấn đề tốc độ, nó là đại diện của các phân tích cú pháp, hay nó là sự phát hiện và phục hồi lỗi?

Cách tốt nhất để biểu diễn nhiều phân tích cú pháp là với một khu rừng được chia sẻ, đơn giản là một ngữ pháp không ngữ cảnh chỉ tạo ra đầu vào của bạn, nhưng với chính xác tất cả các cây phân tích giống như ngữ pháp DSL. Điều đó làm cho nó rất dễ hiểu và xử lý. Để biết thêm chi tiết, tôi đề nghị bạn xem câu trả lời này tôi đã đưa ra trên trang web ngôn ngữ. Tôi hiểu rằng bạn không quan tâm đến việc có được một khu rừng phân tích, nhưng một đại diện thích hợp của khu rừng phân tích có thể giúp bạn đưa ra những thông điệp tốt hơn về những điều mơ hồ là gì. Nó cũng có thể giúp bạn quyết định rằng sự mơ hồ không thành vấn đề trong một số trường hợp (tính kết hợp) nếu bạn muốn làm điều đó.

Bạn đề cập đến các hạn chế về thời gian xử lý ngữ pháp DSL của bạn, nhưng không đưa ra gợi ý nào về kích thước của nó (điều đó không có nghĩa là tôi có thể trả lời với các số liệu bạn đã làm).

Một số xử lý lỗi có thể được tích hợp trong các thuật toán CF chung này theo những cách đơn giản. Nhưng tôi sẽ cần hiểu loại xử lý lỗi mà bạn mong đợi sẽ được khẳng định hơn. Bạn có thể có một số ví dụ.

Tôi hơi khó chịu khi nói nhiều hơn, vì tôi không hiểu đâu là động lực và hạn chế của bạn. Trên cơ sở những gì bạn nói, tôi sẽ gắn bó với CYK (và tôi biết các thuật toán khác và một số thuộc tính của chúng).

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.