Tên cho loại trình phân tích cú pháp này, HOẶC tại sao nó không tồn tại


27

Các trình phân tích cú pháp thông thường tiêu thụ toàn bộ đầu vào của chúng và tạo ra một cây phân tích cú pháp duy nhất. Tôi đang tìm kiếm một luồng tiêu thụ một luồng liên tục và tạo ra một rừng phân tích cú pháp [ sửa: xem thảo luận trong các bình luận về lý do tại sao việc sử dụng thuật ngữ này có thể là độc đáo ]. Ruột của tôi nói rằng tôi không thể là người đầu tiên cần (hoặc nghĩ rằng tôi cần) một trình phân tích cú pháp như vậy, nhưng tôi đã tìm kiếm trong nhiều tháng không có kết quả.

Tôi nhận ra rằng tôi có thể bị ảnh hưởng bởi vấn đề XY. Mục đích cuối cùng của tôi là phân tích một luồng văn bản, bỏ qua phần lớn của nó và tạo ra một luồng các cây phân tích từ các phần được công nhận.

Vì vậy, câu hỏi của tôi là có điều kiện: nếu một lớp các trình phân tích cú pháp với các đặc điểm này tồn tại, nó được gọi là gì? Và nếu không, tại sao không? Sự thay thế là gì? Có lẽ tôi đang thiếu một số cách để tôi có thể làm cho các trình phân tích cú pháp thông thường làm những gì tôi muốn.


1
Về cơ bản, trình phân tích cú pháp của bạn phân tích cú pháp một tài liệu duy nhất và tạo ra một cây phân tích cú pháp, sau đó ngay lập tức bắt đầu phân tích cú pháp một tài liệu khác, v.v. Do đó thiếu một thuật ngữ đặc biệt cho nó.
9000

3
Tôi đã thực hiện Tìm kiếm Google cho "Parse Forest" và phát hiện ra rằng Earley Parser tạo ra chúng.
Robert Harvey

7
Bạn có thể đang tìm kiếm các bộ kết hợp trình phân tích cú pháp đơn âm - nghĩa là, một trình phân tích cú pháp lớn hơn bao gồm một số trình phân tích cú pháp nhỏ hơn. Chúng rất hữu ích cho các tình huống trong đó một "hòn đảo" của một ngôn ngữ được nhúng vào ngôn ngữ khác. Đồng nghiệp cũ của tôi trong nhóm thiết kế C # Luke Hoban có một bài viết hay về họ: blog.msdn.com/b/lukeh/archive 2007/08/19 / Khăn
Eric Lippert

3
Có một số nhầm lẫn. Bạn có nghĩa là bạn muốn một cây phân tích cú pháp cho mỗi tài liệu trong luồng của bạn và chúng tạo thành một khu rừng phân tích. Đó không phải là ý nghĩa thông thường của rừng phân tích. Một rừng phân tích cú pháp là một tập hợp các cây phân tích cho một tài liệu mơ hồ duy nhất (đơn giản hóa một chút) có thể được phân tích cú pháp theo các cách khác nhau. Và đó là những gì tất cả các câu trả lời là về. Là luồng của bạn bao gồm nhiều tài liệu hoàn chỉnh được phân tách bằng rác hoặc là một tài liệu duy nhất đã bị cắt xén một phần. Tài liệu của bạn có được cho là đúng về mặt cú pháp hay không? Các câu trả lời kỹ thuật thích hợp phụ thuộc vào điều đó.
babou

1
Sau đó, quên tất cả các câu trả lời về các khu rừng phân tích, và Earley, GLR, Marpa, các dẫn xuất. Chúng không rõ ràng là những gì bạn muốn trừ khi một lý do khác xuất hiện. Tài liệu của bạn có đúng về mặt cú pháp không? Một số kỹ thuật phân tích cú pháp có thể tạo lại bối cảnh cho các tài liệu bị cắt xén một phần. Bạn có một cú pháp chính xác cho các tài liệu này. Nó là cùng một cho tất cả? Bạn có thực sự muốn các cây phân tích cú pháp, hoặc bạn sẽ hài lòng bằng cách cô lập các tài liệu, và có thể phân tích chúng sau này, riêng rẽ. Tôi nghĩ rằng tôi biết những gì có thể cải thiện quá trình xử lý của bạn, nhưng tôi không chắc bạn có thể đưa nó ra khỏi kệ.
babou

Câu trả lời:


48

Trình phân tích cú pháp trả về kết quả (một phần) trước khi toàn bộ đầu vào được sử dụng được gọi là trình phân tích cú pháp gia tăng . Phân tích cú pháp tăng dần có thể khó khăn nếu có sự mơ hồ cục bộ trong một ngữ pháp chỉ được quyết định sau trong đầu vào. Một khó khăn khác là làm giả những phần của cây phân tích chưa đạt được.

Trình phân tích cú pháp trả về một rừng gồm tất cả các cây phân tích có thể - nghĩa là trả về một cây phân tích cho mỗi dẫn xuất có thể có của một ngữ pháp mơ hồ - được gọi là Tôi không chắc những thứ này đã có tên chưa. Tôi biết rằng trình tạo trình phân tích cú pháp Marpa có khả năng này, nhưng bất kỳ trình phân tích cú pháp dựa trên Earley hoặc GLR nào cũng có thể thực hiện việc này.


Tuy nhiên, bạn dường như không muốn bất kỳ điều đó. Bạn có một luồng có nhiều tài liệu nhúng, với rác ở giữa:

 garbagegarbage{key:42}garbagegarbage[1,2,3]{id:0}garbage...

Bạn dường như muốn một trình phân tích cú pháp bỏ qua rác và (lười biếng) mang lại một chuỗi AST cho mỗi tài liệu. Đây có thể được coi là một trình phân tích cú pháp gia tăng theo nghĩa chung nhất của nó. Nhưng bạn thực sự thực hiện một vòng lặp như thế này:

while stream is not empty:
  try:
    yield parse_document(stream at current position)
  except:
    advance position in stream by 1 character or token

Các parse_docmentchức năng sau đó sẽ là một, không cộng dồn phân tích cú pháp thông thường. Có một khó khăn nhỏ trong việc đảm bảo rằng bạn đã đọc đủ luồng đầu vào để phân tích thành công. Làm thế nào điều này có thể được xử lý tùy thuộc vào loại trình phân tích cú pháp bạn đang sử dụng. Khả năng bao gồm phát triển bộ đệm trên các lỗi phân tích cú pháp nhất định hoặc sử dụng mã thông báo lười biếng.

Mã thông báo lười biếng có lẽ là giải pháp thanh lịch nhất do luồng đầu vào của bạn. Thay vì có một pha lexer tạo ra một danh sách các mã thông báo cố định, trình phân tích cú pháp sẽ lười biếng yêu cầu mã thông báo tiếp theo từ một cuộc gọi lại lexer [1] . Các lexer sau đó sẽ tiêu thụ càng nhiều luồng khi cần thiết. Theo cách này, trình phân tích cú pháp chỉ có thể thất bại khi đạt đến điểm cuối thực sự của luồng hoặc khi xảy ra lỗi phân tích cú pháp thực sự (nghĩa là chúng tôi đã bắt đầu phân tích cú pháp trong khi vẫn còn rác).

[1] một lexer điều khiển gọi lại cũng là một ý tưởng tốt trong các bối cảnh khác, bởi vì điều này có thể tránh được một số vấn đề với kết hợp mã thông báo dài nhất .

Nếu bạn biết loại tài liệu nào bạn đang tìm kiếm, bạn có thể tối ưu hóa việc bỏ qua để chỉ dừng lại ở các vị trí đầy hứa hẹn. Ví dụ: một tài liệu JSON luôn bắt đầu bằng ký tự {hoặc [. Do đó, rác là bất kỳ chuỗi nào không chứa các ký tự này.


5
Mã giả của bạn thực sự là những gì tôi đã làm, nhưng tôi nghĩ đó chỉ là một bản hack xấu xí. Trình phân tích cú pháp đưa ra hai loại ngoại lệ ( NO_MATCHUNDERFLOW) cho phép tôi phân biệt xem tôi nên nâng cao vị trí luồng hay chờ thêm đầu vào.
Kevin Krumwiede

5
@Kevin: Tôi cũng sử dụng tính năng này với một số tính năng an toàn để xử lý dữ liệu đến từ mạng ở định dạng độc quyền. Không có gì hacky về nó!
Cuộc đua nhẹ nhàng với Monica

5

Không có một tên cụ thể cho trình phân tích cú pháp thực hiện điều này. Nhưng tôi sẽ làm nổi bật một thuật toán thực hiện điều này: phân tích cú pháp bằng các dẫn xuất .

Nó tiêu thụ đầu vào, một mã thông báo tại một thời điểm. Nó sẽ tạo ra một khu rừng phân tích ở cuối đầu vào. Ngoài ra, bạn cũng có thể có được toàn bộ khu rừng phân tích trong khi ở giữa phân tích cú pháp (một phân tích cú pháp một phần ).

Phân tích cú pháp với các dẫn xuất xử lý các ngữ pháp không ngữ cảnh và sẽ tạo ra một rừng phân tích cú pháp cho các ngữ pháp mơ hồ.

Đó thực sự là một lý thuyết tao nhã, nhưng chỉ mới ở giai đoạn sơ khai và không được triển khai rộng rãi. Matt Might có một danh sách các liên kết đến các triển khai khác nhau trong Scala / Vợt / v.v.

Lý thuyết sẽ dễ học hơn nếu bạn bắt đầu bằng nhận dạng với các công cụ phái sinh (nghĩa là bắt đầu bằng việc sử dụng các công cụ phái sinh của ngôn ngữ , với mục tiêu nhận ra một số đầu vào để xác định xem nó có hợp lệ hay không), sau đó thay đổi chương trình để phân tích bằng các công cụ phái sinh ( nghĩa là thay đổi nó để thay vì lấy đạo hàm của ngôn ngữ , nó lấy đạo hàm của trình phân tích cú pháp và tính toán một khu rừng phân tích cú pháp).


4
Downvoter: bạn có thể vui lòng giải thích những gì xứng đáng với một downvote? Nếu có điều gì đó tôi cần sửa chữa hoặc cải thiện, chắc chắn sẽ rất tốt nếu biết.
Bắp ngô

Tôi không phải là người hạ cấp, và tôi sẽ không mơ đến việc hạ cấp mà không có bình luận. Nhưng bài viết hấp dẫn của bạn không có tham chiếu đến nhiều trình phân tích cú pháp hiện có đạt được kết quả tương tự, liên quan đến độ phức tạp và phân tích rừng. Lập trình chức năng là rất tốt, nhưng so sánh một kết quả với các tài liệu hiện có về chủ đề này cũng tốt. Làm thế nào thuận tiện là rừng phân tích của bạn để sử dụng thêm?
babou

@babou: đối với hồ sơ, tôi không phải là tác giả của blog / tờ báo đó. Nhưng vâng, tôi đồng ý tôi có thể thêm chi tiết so sánh thuật toán này với các thuật toán khác và giải thích chi tiết hơn. Matt Might có cả một bài giảng về nó , nhưng thật tuyệt khi hợp nhất nó vào câu trả lời này. Nếu tôi có thời gian tôi sẽ cố gắng mở rộng câu trả lời này.
Bắp ngô

1
Đừng dành quá nhiều thời gian cho việc mở rộng nó. Theo như tôi có thể nói, đó không phải là những gì OP đang theo đuổi. Câu hỏi của anh đòi hỏi phải đọc cẩn thận. Việc anh ta sử dụng rừng phân tích không phải của bạn. - - Liên quan đến các công cụ phái sinh ... nghe có vẻ thú vị, nhưng người ta phải liên hệ nó với công việc trước đây ... và có một cơ thể quan trọng của nó. Nhưng tôi không có ý trong câu trả lời này, mà trong các bài viết của M Might, hoặc blog của anh ấy.
babou

2

Khác xa với lý tưởng, nhưng tôi đã thấy nó được thực hiện nhiều lần: tại mỗi dòng đầu vào hãy thử phân tích cú pháp. nếu thất bại, giữ dòng và thêm cái tiếp theo. Trong mã giả:

buffer = ''
for each line from input:
    buffer = buffer + line
    if can parse buffer:
        emit tree
        buffer = ''

Vấn đề lớn là trong một số ngôn ngữ bạn không thể biết nếu một biểu thức đã hoàn thành trước khi đọc dòng tiếp theo. Trong trường hợp đó, có vẻ như bạn có thể đọc phần tiếp theo và kiểm tra xem đó là một khởi đầu hợp lệ hay tiếp tục hợp lệ ... Nhưng để làm được điều đó, bạn cần cú pháp ngôn ngữ chính xác

Tồi tệ hơn, trong các ngôn ngữ đó, không khó để tạo ra một trường hợp bệnh lý không thể được phân tích cú pháp cho đến khi kết thúc tập tin, ngay cả khi đó không phải là một câu lệnh dài.


0

Tóm lại

Dường như giải pháp nhanh cho vấn đề của bạn là xác định REGEX hoặc FSA (máy tự động trạng thái hữu hạn), nhận ra tất cả các khởi đầu có thể của tài liệu (cho phép dương tính giả, điều đó thực sự không tương ứng với tài liệu). Sau đó, bạn có thể chạy nó rất nhanh trên đầu vào của mình để xác định vị trí tiếp theo nơi tài liệu có thể bắt đầu với một vài lỗi. Nó có thể gây ra một vài vị trí sai lầm khi bắt đầu tài liệu, nhưng chúng sẽ được trình phân tích cú pháp nhận ra và từ bỏ.

Vì vậy, Finite State Automaton có thể là tên trình phân tích cú pháp mà bạn đang tìm kiếm. :)

Vấn đề

Luôn luôn khó hiểu một vấn đề thực tế, đặc biệt là khi từ vựng có thể có nhiều cách giải thích. Rừng phân tích cú pháp được đặt ra (afaik) để phân tích ngữ cảnh không có ngữ cảnh (CF) cho các câu mơ hồ có nhiều phân tích cú pháp. Nó có thể được khái quát phần nào để phân tích một mạng các câu hoặc các loại ngữ pháp khác. Do đó, tất cả các câu trả lời về Earley, GLR, Marpa và các trình phân tích cú pháp phái sinh (có nhiều câu trả lời khác) không liên quan trong trường hợp này.

Nhưng đó rõ ràng không phải là những gì bạn có trong tâm trí. Bạn muốn phân tích một chuỗi duy nhất là một chuỗi các tài liệu không rõ ràng và lấy một cây phân tích cho mỗi chuỗi , hoặc một loại biểu diễn có cấu trúc nào đó, vì bạn không thực sự nói cách xác định cú pháp của tài liệu của bạn, nó nằm ở đâu một quan điểm ngôn ngữ chính thức. Những gì bạn có là một thuật toán và các bảng sẽ thực hiện công việc phân tích cú pháp khi bắt đầu ở phần đầu của tài liệu. Vì vậy, nó được.

Vấn đề thực tế là luồng tài liệu của bạn chứa rác đáng kể ngăn cách các tài liệu. Và dường như khó khăn của bạn là quét rác này đủ nhanh. Kỹ thuật hiện tại của bạn là bắt đầu từ đầu và cố gắng quét từ ký tự đầu tiên và bỏ qua để khởi động lại ở ký tự tiếp theo bất cứ khi nào nó thất bại, cho đến khi bạn quét toàn bộ tài liệu. Sau đó, bạn lặp lại nêu từ ký tự đầu tiên sau khi tài liệu vừa được quét.

Đó cũng là giải pháp được đề xuất bởi @amon trong phần thứ hai của câu trả lời của ông .

Nó có thể không phải là một giải pháp rất nhanh (tôi không có cách nào để kiểm tra), vì không chắc là mã của trình phân tích cú pháp được tối ưu hóa để bắt đầu rất hiệu quả khi bắt đầu một tài liệu. Trong sử dụng bình thường, nó chỉ thực hiện điều này một lần, do đó nó không phải là điểm nóng theo quan điểm tối ưu hóa. Do đó, hạnh phúc vừa phải của bạn với giải pháp này không quá ngạc nhiên.

Vì vậy, những gì bạn thực sự cần là một thuật toán có thể nhanh chóng tìm thấy sự khởi đầu của một tài liệu bắt đầu với một khối rác. Và bạn thật may mắn: những thuật toán như vậy có tồn tại. Và tôi chắc chắn bạn biết điều đó: nó được gọi là tìm kiếm REGEX.

Giải pháp đơn giản

Những gì bạn phải làm là phân tích đặc điểm kỹ thuật của tài liệu của bạn để tìm cách các tài liệu này bắt đầu. Tôi không thể chính xác cho bạn biết làm thế nào, vì tôi không chắc cách thức đặc tả cú pháp của chúng được tổ chức chính thức. Có thể tất cả đều bắt đầu bằng một số từ trong danh sách hữu hạn, có thể trộn lẫn với một số dấu câu hoặc số. Đó là để bạn kiểm tra.

Những gì bạn phải làm là xác định một máy tự động trạng thái hữu hạn (FSA) hoặc tương đương với hầu hết các lập trình viên một biểu thức chính quy (REGEX) có thể nhận ra một vài ký tự đầu tiên của tài liệu: càng nhiều, càng tốt, nhưng nó không cần phải rất lớn (vì điều đó có thể mất thời gian và không gian). Điều này tương đối dễ thực hiện từ đặc điểm kỹ thuật của tài liệu của bạn và có thể được thực hiện tự động với chương trình đọc thông số kỹ thuật của tài liệu của bạn.

Khi bạn đã tạo regrec của mình, bạn có thể chạy nó trên luồng đầu vào của mình để nhanh chóng bắt đầu tài liệu đầu tiên (hoặc tiếp theo) như sau:

Tôi giả sử:
- docstartlà một biểu thức chính phù hợp với phần đầu của tất cả các tài liệu
- search(regex, stream)là một hàm tìm kiếm streammột chuỗi con phù hợp regex. Khi nó trở lại, luồng được giảm xuống thành dòng phụ hậu tố bắt đầu từ đầu chuỗi con phù hợp đầu tiên hoặc luồng trống không tìm thấy kết quả khớp.
- parse(stream)cố gắng phân tích tài liệu từ đầu luồng (phần còn lại của nó) và trả về cây phân tích theo bất kỳ định dạng nào hoặc không thành công. Khi nó trở lại, luồng được giảm xuống thành dòng phụ hậu tố bắt đầu tại vị trí ngay sau phần cuối của tài liệu được phân tích cú pháp. Nó gọi một ngoại lệ nếu phân tích cú pháp thất bại.

forest = empty_forest
search(docstart, stream)
while stream is not empty:
  try:
    forest = forest + parse(stream)
  except
    remove first character from stream
  search(docstart, stream)

Lưu ý rằng việc loại bỏ ký tự đầu tiên là cần thiết để tìm kiếm tiếp theo sẽ không tìm thấy lại cùng một kết quả.

Tất nhiên, rút ​​ngắn luồng là một hình ảnh. Nó có thể chỉ là một chỉ mục trên luồng.

Một lưu ý cuối cùng là regex của bạn không cần phải quá chính xác, miễn là nó nhận ra tất cả sự khởi đầu. Nếu đôi khi nó nhận ra một chuỗi không thể là phần đầu của tài liệu (dương tính giả), thì hình phạt duy nhất là chi phí của một cuộc gọi vô dụng đến trình phân tích cú pháp.

Vì vậy, điều đó có thể giúp đơn giản hóa regex, nếu hữu ích.

Về khả năng của một giải pháp nhanh hơn

Các giải pháp trên nên hoạt động khá tốt trong hầu hết các trường hợp. Tuy nhiên, nếu bạn thực sự có rất nhiều rác và terabyte tệp để xử lý, có thể có các thuật toán khác chạy nhanh hơn.

Ý tưởng được bắt nguồn từ thuật toán tìm kiếm chuỗi Boyer-Moore . Thuật toán này có thể tìm kiếm một luồng cho một chuỗi cực nhanh vì nó sử dụng phân tích cấu trúc của chuỗi để bỏ qua việc đọc hầu hết luồng, nhảy qua các đoạn mà không cần nhìn vào chúng. Đây là thuật toán tìm kiếm nhanh nhất cho một chuỗi.

Khó khăn là sự thích ứng của nó với regex tìm kiếm, thay vì một chuỗi đơn lẻ, có vẻ rất tinh vi và có thể không hoạt động tốt, tùy thuộc vào các tính năng của regex bạn đang xem xét. Điều đó có thể lần lượt phụ thuộc vào cú pháp của các tài liệu bạn đang phân tích cú pháp. Nhưng đừng tin tôi quá nhiều về điều này vì tôi không có thời gian để đọc kỹ các tài liệu tôi tìm thấy.

Tôi sẽ để lại cho bạn một hoặc hai con trỏ tôi tìm thấy trên web, bao gồm một tài liệu rõ ràng là một tài liệu nghiên cứu được giới thiệu , nhưng bạn nên xem đây là suy đoán nhiều hơn, có thể là nghiên cứu, chỉ được xem xét nếu bạn gặp vấn đề về hiệu suất mạnh. Và có lẽ không có chương trình kệ nào sẽ làm điều đó.


-2

Những gì bạn đang mô tả có thể được mô tả là SAX so với SOM.

SAX - (API đơn giản cho XML) là API trình phân tích cú pháp truy cập tuần tự sự kiện được phát triển bởi danh sách gửi thư XML-DEV cho các tài liệu XML.

SOM - (Mô hình đối tượng lược đồ XML) truy cập ngẫu nhiên vào biểu diễn bộ nhớ của tệp XML

Có các triển khai của cả hai loại trong C # và Java và có thể nhiều loại khác. Thông thường XSD hoặc DTD là tùy chọn.

Niềm vui của SAX là nó có dung lượng bộ nhớ thấp, rất phù hợp với các tệp XML lớn. Sự đánh đổi là việc truy cập ngẫu nhiên bằng SAX là không tồn tại hoặc chậm, và tệ hơn là thời gian phát triển thường đáng kể hơn so với SOM. Vấn đề rõ ràng với SOM là tiềm năng yêu cầu RAM lớn.

Câu trả lời này không áp dụng cho tất cả các nền tảng và tất cả các ngôn ngữ.


1
Tại sao bạn nghĩ OP đang phân tích cú pháp XML?
Dan Pichelman

1
Điều này không trả lời câu hỏi.

@Snowman Hầu như không có gì cho đến nay là trả lời câu hỏi, kể cả nửa đầu của câu trả lời được chấp nhận. Không có điểm trong việc chọn bất cứ ai. Câu hỏi cần đọc cẩn thận.
babou

@babou Tôi không chọn ai cả, tôi đang giải thích về downvote của mình.

@Snowman giải thích downvote của tôi . Đó là công bằng, và tôi muốn nhiều người dùng sẽ làm điều đó. Tôi không phải là người bản ngữ: chọn anh ấy là một biểu hiện quá mạnh mẽ. Chỉ là mọi người đã và đang đưa ra những giả định không chính đáng. Vì vậy, nó thậm chí không đáng chú ý. Đúng là cái này có vẻ hơi nhiều so với cái khác.
babou
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.