Lexing ne zaman yeterli, EBNF'ye ne zaman ihtiyacınız var?
EBNF , gramerlerin gücüne gerçekten fazla bir şey katmıyor. Standart Chomsky'nin Normal Form (CNF) dilbilgisi kurallarına göre sadece bir kolaylık / kısayol gösterimi / "sözdizimsel şeker" dir . Örneğin, EBNF alternatifi:
S --> A | B
CNF'de her alternatif üretimi ayrı ayrı listeleyerek başarabilirsiniz:
S --> A // `S` can be `A`,
S --> B // or it can be `B`.
EBNF'den isteğe bağlı eleman:
S --> X?
CNF'de null edilebilir bir üretim, yani boş bir dize ile değiştirilebilen (burada sadece boş üretim ile gösterilir; diğerleri epsilon veya lambda veya çapraz daire kullanır) kullanarak elde edebilirsiniz:
S --> B // `S` can be `B`,
B --> X // and `B` can be just `X`,
B --> // or it can be empty.
B
Yukarıdaki sonuncusu gibi bir formata "silme" denir, çünkü diğer prodüksiyonlarda ne anlama geldiğini silebilir (başka bir şey yerine boş bir dize üretebilir).
EBNF'den sıfır veya daha fazla tekrar:
S --> A*
özyinelemeli üretimi, yani kendisini içinde bir yere gömen bir nesneyi elde edebilirsiniz . İki şekilde yapılabilir. Birincisi sol özyineleme (genellikle kaçınılmalıdır, çünkü Yukarıdan Aşağı Özyinelemeli İniş ayrıştırıcıları ayrıştıramıyor):
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.
Sadece boş bir dize (sonuçta) ve ardından sıfır veya daha fazla A
s ürettiğini bilerek , aynı dize ( ancak aynı dil değil! ) Sağ özyineleme kullanılarak ifade edilebilir :
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.
+
EBNF'den bir veya daha fazla tekrar söz konusu olduğunda :
S --> A+
birini çarpanlarına ayırıp daha önce olduğu gibi A
kullanarak yapabilirsiniz *
:
S --> A A*
CNF'de böyle ifade edebilirsiniz (burada doğru özyineleme kullanıyorum; diğerini egzersiz olarak anlamaya çalışın):
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`.
Bunu bilerek, artık normal bir ifade için bir dilbilgisini (yani, normal dilbilgisi ) yalnızca terminal sembollerinden oluşan tek bir EBNF üretiminde ifade edilebilecek bir dilbilgisi tanıyabilirsiniz . Daha genel olarak, bunlara benzer prodüksiyonlar gördüğünüzde düzenli gramerleri tanıyabilirsiniz:
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.
Yani, sadece boş dizeler, terminal sembolleri, ikameler ve durum değişiklikleri için basit terminaller kullanma ve yalnızca tekrarlama elde etmek için özyineleme kullanma (sadece doğrusal özyineleme olan yineleme - ağaç benzeri olmayan dal). Bunların üzerinde daha gelişmiş bir şey yok, o zaman düzenli bir sözdizimi olduğundan eminsiniz ve bunun için sadece lexer ile gidebilirsiniz.
Ancak sözdiziminiz, aşağıdaki gibi ağaç benzeri, kendine benzer, iç içe yapılar oluşturmak için özyinelemeyi önemsiz olmayan bir şekilde kullandığında:
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.
o zaman bunun düzenli ifade ile yapılamayacağını kolayca görebilirsiniz, çünkü bunu hiçbir şekilde tek bir EBNF üretiminde çözemezsiniz; S
süresiz olarak ikame edersiniz , bu da her iki tarafa da her zaman başka bir a
s ve b
s ekler . Lexers (daha spesifik olarak: lexers tarafından kullanılan Sonlu Durum Otomataları) rastgele sayıya (sonlu, hatırladınız mı?) Sayamaz, bu yüzden kaç tane a
s ile eşit olarak eşleşecekleri kaç tane olduğunu bilmezler b
. Bunun gibi gramerlere bağlamsız gramerler denir (en azından) ve ayrıştırıcıya ihtiyaç duyarlar.
Bağlamdan bağımsız gramerlerin ayrıştırılması iyi bilinmektedir, bu nedenle programlama dillerinin sözdizimini tanımlamak için yaygın olarak kullanılırlar. Ama dahası da var. Bazen daha genel bir dilbilgisi gerekir - aynı anda, bağımsız olarak sayılacak daha fazla şeyiniz olduğunda. Örneğin, bir kişinin yuvarlak parantezleri ve kare parantezleri aralıklı olarak kullanabileceği bir dil tanımlamak istediğinizde, ancak birbirleriyle doğru bir şekilde eşleştirilmeleri gerekir (parantezli parantez, yuvarlak yuvarlak). Bu tür gramer içeriğe duyarlı olarak adlandırılır . Solda (oktan önce) birden fazla sembolü olmasıyla bunu tanıyabilirsiniz. Örneğin:
A R B --> A S B
Soldaki bu ek sembolleri kuralı uygulamak için bir "bağlam" olarak düşünebilirsiniz. Bazı ön şartlar var olabilir, vb Hedefşartlar Örneğin, yukarıda kural yerine başkalarını R
içine S
, ancak aradaki var sadece A
ve B
bu bırakarak A
ve B
değişmeden kendilerini. Bu tür bir sözdiziminin ayrıştırılması gerçekten zordur, çünkü tam gelişmiş bir Turing makinesine ihtiyaç duyar. Bu tamamen başka bir hikaye, bu yüzden burada bitireceğim.