Khi nào đủ từ vựng, khi nào bạn cần EBNF?
EBNF thực sự không bổ sung nhiều vào sức mạnh của ngữ pháp. Đó chỉ là một ký hiệu tiện lợi / phím tắt / "đường cú pháp" so với các quy tắc ngữ pháp chuẩn của Chomsky's Form Form (CNF). Ví dụ: thay thế EBNF:
S --> A | B
bạn có thể đạt được trong CNF bằng cách liệt kê riêng từng sản phẩm thay thế:
S --> A // `S` can be `A`,
S --> B // or it can be `B`.
Phần tử tùy chọn từ EBNF:
S --> X?
bạn có thể đạt được trong CNF bằng cách sử dụng một nullable sản xuất, có nghĩa là, một trong những có thể được thay thế bằng một chuỗi rỗng (biểu thị bằng chỉ sản xuất có sản phẩm nào ở đây, một số khác sử dụng epsilon hoặc lambda hoặc hình tròn vượt qua):
S --> B // `S` can be `B`,
B --> X // and `B` can be just `X`,
B --> // or it can be empty.
Một sản phẩm ở dạng như cái cuối cùng B
ở trên được gọi là "erasure", bởi vì nó có thể xóa bất cứ thứ gì nó đại diện cho các sản phẩm khác (sản phẩm là một chuỗi rỗng thay vì một thứ khác).
Không lặp lại hoặc nhiều hơn từ EBNF:
S --> A*
bạn có thể obtan bằng cách sử dụng sản xuất đệ quy , nghĩa là, một thứ tự nhúng vào đâu đó trong đó. Nó có thể được thực hiện theo hai cách. Đầu tiên là đệ quy trái (thường nên tránh, bởi vì trình phân tích cú pháp gốc đệ quy từ trên xuống không thể phân tích cú pháp):
S --> S A // `S` is just itself ended with `A` (which can be done many times),
S --> // or it can begin with empty-string, which stops the recursion.
Biết rằng nó chỉ tạo ra một chuỗi rỗng (cuối cùng) theo sau là 0 hoặc nhiều A
s, cùng một chuỗi ( nhưng không phải cùng ngôn ngữ! ) Có thể được biểu thị bằng cách sử dụng đệ quy đúng :
S --> A S // `S` can be `A` followed by itself (which can be done many times),
S --> // or it can be just empty-string end, which stops the recursion.
Và khi nói đến +
một hoặc nhiều lần lặp lại từ EBNF:
S --> A+
nó có thể được thực hiện bằng cách bao thanh toán một A
và sử dụng *
như trước:
S --> A A*
mà bạn có thể diễn đạt trong CNF như vậy (tôi sử dụng đệ quy đúng ở đây; cố gắng tự mình tìm ra một bài tập khác như một bài tập):
S --> A S // `S` can be one `A` followed by `S` (which stands for more `A`s),
S --> A // or it could be just one single `A`.
Biết rằng, bây giờ bạn có thể nhận ra một ngữ pháp cho một biểu thức chính quy (nghĩa là ngữ pháp thông thường ) là một ngữ pháp có thể được thể hiện trong một sản phẩm EBNF duy nhất chỉ bao gồm các ký hiệu đầu cuối. Tổng quát hơn, bạn có thể nhận ra các ngữ pháp thông thường khi bạn thấy các sản phẩm tương tự như sau:
A --> // Empty (nullable) production (AKA erasure).
B --> x // Single terminal symbol.
C --> y D // Simple state change from `C` to `D` when seeing input `y`.
E --> F z // Simple state change from `E` to `F` when seeing input `z`.
G --> G u // Left recursion.
H --> v H // Right recursion.
Đó là, chỉ sử dụng các chuỗi rỗng, các ký hiệu đầu cuối, các đầu cuối đơn giản để thay thế và thay đổi trạng thái và chỉ sử dụng đệ quy để đạt được sự lặp lại (lặp, chỉ là đệ quy tuyến tính - một đệ quy không giống như nhánh cây). Không có gì cao cấp hơn những thứ này, sau đó bạn chắc chắn đó là một cú pháp thông thường và bạn có thể đi với chỉ từ vựng cho điều đó.
Nhưng khi cú pháp của bạn sử dụng đệ quy theo cách không tầm thường, để tạo ra các cấu trúc lồng vào nhau, giống như cây, giống như sau:
S --> a S b // `S` can be itself "parenthesized" by `a` and `b` on both sides.
S --> // or it could be (ultimately) empty, which ends recursion.
sau đó bạn có thể dễ dàng thấy rằng điều này không thể được thực hiện bằng biểu thức chính quy, bởi vì bạn không thể giải quyết nó thành một sản phẩm EBNF theo bất kỳ cách nào; bạn sẽ kết thúc với việc thay thế S
vô thời hạn, điều này sẽ luôn thêm một a
s và b
s khác ở cả hai bên. Các phần tử (cụ thể hơn: Automata State Finata được sử dụng bởi các nhà từ vựng) không thể được tính vào số tùy ý (chúng là hữu hạn, nhớ không?), Vì vậy chúng không biết có bao nhiêu a
s ở đó để khớp chúng với rất nhiều b
s. Các ngữ pháp như thế này được gọi là ngữ pháp không ngữ cảnh (ít nhất là) và chúng yêu cầu một trình phân tích cú pháp.
Ngữ pháp không ngữ cảnh được biết đến để phân tích cú pháp, vì vậy chúng được sử dụng rộng rãi để mô tả cú pháp ngôn ngữ lập trình. Nhưng còn nhiều hơn thế. Đôi khi cần một ngữ pháp tổng quát hơn - khi bạn có nhiều thứ để đếm cùng một lúc, một cách độc lập. Ví dụ, khi bạn muốn mô tả một ngôn ngữ trong đó người ta có thể sử dụng dấu ngoặc tròn và dấu ngoặc vuông xen kẽ, nhưng chúng phải được ghép nối chính xác với nhau (niềng răng có niềng răng, tròn với vòng tròn). Loại ngữ pháp này được gọi là nhạy cảm ngữ cảnh . Bạn có thể nhận ra nó bằng cách nó có nhiều hơn một biểu tượng bên trái (trước mũi tên). Ví dụ:
A R B --> A S B
Bạn có thể nghĩ về các biểu tượng bổ sung này ở bên trái như một "bối cảnh" để áp dụng quy tắc. Có thể có một số điều kiện tiên quyết, postconditions vv Ví dụ, các quy tắc trên sẽ thay thế R
vào S
, nhưng chỉ khi nó ở giữa A
và B
, để lại những A
và B
bản thân không thay đổi. Loại cú pháp này thực sự khó phân tích, bởi vì nó cần một máy Turing đầy đủ. Đó là một câu chuyện hoàn toàn khác, vì vậy tôi sẽ kết thúc ở đây.