Ayrıştırıcı için bir gramer nasıl belirtmeliyim?


12

Uzun yıllardır program yapıyorum, ancak beni hala uzun süren bir görev, bir ayrıştırıcı için bir dilbilgisi belirtmektir ve bu aşırı çabadan sonra bile, bulduğum dilbilgisinin iyi olduğundan emin değilim ( makul herhangi bir "iyi" ölçüsü ile).

Bir dilbilgisi belirtme sürecini otomatikleştirmek için bir algoritma olmasını beklemiyorum, ancak umarım mevcut yaklaşımımın tahmin ve deneme yanılmalarının çoğunu ortadan kaldıran sorunu yapılandırmanın yolları vardır.

İlk düşüncem ayrıştırıcılar hakkında okumaktı ve ben bunlardan bazılarını yaptım, ancak bu konuda okuduğum her şey grameri verilmiş olarak alıyor (veya birinin muayene ile belirleyebileceği kadar önemsiz) ve bu dilbilgisini ayrıştırıcıya dönüştürme sorunu. Hemen problemle ilgileniyorum: ilk etapta gramer nasıl belirlenir.

Öncelikle , somut örneklerin (pozitif ve negatif) bir koleksiyonunu temsil eden bir gramer belirleme problemiyle ilgileniyorum . Bu, yeni bir sözdizimi tasarlama sorunundan farklıdır . Bu ayrımı vurguladığı için Macneil'e teşekkürler.

Bir dilbilgisi ve bir sözdizimi arasındaki farkı gerçekten hiç takdir etmedim, ama şimdi görmeye başladığımdan, öncelikle açıklamayı zorlayacak bir dilbilgisi belirleme sorunuyla ilgilendiğimi söyleyerek ilk açıklamamı keskinleştirebilirim. önceden tanımlanmış sözdizimi: benim durumumda, bu sözdiziminin temelinin genellikle olumlu ve olumsuz örneklerin bir koleksiyonudur.

Ayrıştırıcı için dilbilgisi nasıl belirlenir? En iyi uygulamaları, tasarım metodolojilerini ve bir ayrıştırıcı için dilbilgisi belirtme hakkında diğer yararlı bilgileri tanımlamak için fiili standart olan bir kitap veya referans var mı? Ayrıştırıcı dilbilgisi hakkında okurken hangi noktalara odaklanmalıyım?


1
Sorunu, gerçek sorununa odaklanmak için biraz düzenledim. Bu site tam olarak gramerler ve ayrıştırıcılar hakkında sorularınızı sorabileceğiniz ve uzman cevapları alabileceğiniz bir yerdir. Bakmaya değer dış kaynaklar varsa, doğrudan size yardımcı olan cevaplarda doğal olarak ortaya çıkacaktır.
Adam Lear

8
@kjo Bu talihsiz. İstediğiniz tek şey bir referans listesiyse, Stack Exchange'i tam potansiyeli için kullanmıyorsunuzdur. Meta sorununuz, siteyi istendiği gibi kullanmıyor. Liste soruları Stack Exchange'de neredeyse evrensel olarak cesaret kırıldı , çünkü Soru-Cevap modeline çok iyi uymuyorlar. Zihniyetinizi , soruları, fikirleri veya fikirleri değil, cevapları olan sorular sormaya kaydırmanızı şiddetle tavsiye ederim .
Adam Lear

3
@kjo Bu kesinlikle bir soru, ama Stack Exchange'de sorulacak doğru soru değil . SE referans listeleri oluşturmak için burada değil. Referans olmak için burada . Daha ayrıntılı bir açıklama için lütfen yorumumda bağlandığım meta yayını okuyun .
Adam Lear

5
@kjo: Lütfen cesaretini kırma! Anna'nın düzenlemeleri sorunuzun özünü ve kalbini korudu ve sorunuzu Programcılar'da beklediğimiz formdan daha fazla hale getirerek size yardımcı oldu. Aradığınız kesin referansları bilmiyorum, ancak cevap verebildim. [OTOH, böyle bir referansı bilseydim, kesinlikle dahil ederdim.] Benim gibi cevapları daha fazla teşvik etmek istiyoruz, çünkü bu özel durumda, aradığın şey için bir referans olduğuna inanmıyorum, sadece başkalarıyla konuşma deneyimi.
Macneil

4
@kjo Anna'nın düzenlemelerine geri döndüm ve kitap önerileriyle ilgili kılavuzumuza dayanarak kanonik referans için belirli bir çağrı eklemeye çalıştım : verilen cevaplarda çok iyi bilgiler var ve bunları sadece kitap bulmakla ilgili bir israf olacaktır. Şimdi hepimiz düzenleme savaşıyla durabilirsek, bu harika olurdu.

Yanıtlar:


12

Örnek dosyalardan, bu örneklerden ne kadar genelleştirmek istediğinize bağlı olarak karar vermeniz gerekecektir. Aşağıdaki üç örneğe sahip olduğunuzu varsayalım: (her biri ayrı bir dosyadır)

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

Bu örnekleri kabul edecek iki gramer belirtebilirsiniz:

Önemsiz Gramer Bir:

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

Önemsiz Dilbilgisi İki:

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

Birincisi önemsiz çünkü sadece üç örneği kabul ediyor . İkincisi önemsizdir, çünkü bu token türlerini kullanabilecek her şeyi kabul eder . [Bu tartışma için tokenizer tasarımı hakkında fazla endişe duymadığınızı varsayacağım: Jetonlarınız olarak tanımlayıcıları, sayıları ve noktalama işaretlerini varsaymak basittir ve herhangi bir kodlama dilinden herhangi bir token setini ödünç alabilirsiniz her neyse.]

Dolayısıyla, izlemeniz gereken prosedür yüksek düzeyde başlamak ve "her bir örnekten kaç tanesine izin vermek istiyorum?" Sözdizimsel bir yapı, methodbir sınıftaki s gibi herhangi bir sayıda yinelemeyi anlamlı kılabilirse , bu formda bir kural istersiniz:

methods ::= method methods | empty

EBNF'de daha iyi ifade edilen:

methods ::= {method}

Sadece sıfır veya bir örnek istediğinizde ( extendsbir Java sınıfının yan tümcesinde olduğu gibi yapının isteğe bağlı olduğu anlamına gelir ) veya bir veya daha fazla örneğe (bir bildirimdeki değişken başlatıcıda olduğu gibi) izin vermek istediğinizde muhtemelen açıktır. ). Öğeler arasında ayırıcı gerektirmesi ( ,bağımsız değişken listesinde olduğu gibi), her öğeden sonra bir sonlandırıcı gerektirmesi ( ;ifadeleri ayırmak için olduğu gibi ) veya ayırıcı veya sonlandırıcı gerektirmemesi (duruma göre) sınıftaki yöntemlerle).

Dilinizde aritmetik ifadeler kullanılıyorsa, mevcut bir dilin öncelik kurallarından kopyalamanız kolay olacaktır. C'nin ifade kuralları gibi iyi bilinen bir şeye bağlı kalmak en iyisidir, ancak egzotik bir şeye gitmekten başka, ancak sadece diğer her şeyin eşit olması şartıyla.

Öncelik sorunları (birbirleriyle ayrıştırılanlar) ve tekrarlama sorunlarına (her bir öğeden kaç tane oluşmalı, nasıl ayrılırlar?) Ek olarak, düzen hakkında da düşünmeniz gerekir: Bir şey her zaman başka bir şeyden önce görünmeli mi? Bir şey dahil edilirse, başka bir şey hariç tutulmalı mı?

Bu noktada, bazı kuralları dilbilgisel olarak uygulamak için cazip gelebilirsiniz, bir Personyaş belirtilirse, doğum tarihlerinin de belirtilmesine izin vermek istemediğiniz gibi bir kural . Bunu yapmak için dilbilginizi oluşturabilirsiniz, ancak her şey ayrıştırıldıktan sonra bunu bir "anlamsal denetim" geçişiyle uygulamak daha kolay olabilir. Bu, dilbilgisini daha basit tutar ve bence, kuralın ihlal edildiği zaman için daha iyi hata mesajları sağlar.


1
Daha iyi hata mesajları için +1. 10 veya 10 milyon kullanıcısı olsun, dilinizin çoğu kullanıcısı uzman olmayacaktır. Ayrıştırma teorisi bu yönü çok uzun zamandır ihmal etmiştir.
MSalters

10

Ayrıştırıcı için dilbilgisini nasıl belirleyeceğimi nereden öğrenebilirim?

En ayrıştırıcı jeneratörler için, genellikle bir varyasyonunu var Backus Naur 'ın <nonterminal> ::= expressionbiçimi. Böyle bir şey kullandığınızı ve ayrıştırıcılarınızı elle oluşturmaya çalışmadığınız varsayımına devam edeceğim. Sözdiziminin verildiği bir format için ayrıştırıcı üretebiliyorsanız (aşağıda örnek bir sorun ekledim), dilbilgisi belirtmek sizin probleminiz değildir.

Sanırım karşı olduğunuzu düşündüğüm bir dizi örnekten sözdizimini ayırmak, ki bu gerçekten ayrıştırmaktan daha fazla örüntü tanıma. Buna başvurmak zorunda kalırsanız, verilerinizi sağlayan her kimse size sözdizimini veremez, çünkü biçiminde iyi bir tutuş yoktur. Geri itme ve size resmi bir tanım vermelerini söyleme seçeneğiniz varsa, bunu yapın. Bozuk girdiyi kabul eden veya iyi girdiyi reddeden çıkarılan bir sözdizimine dayanan bir ayrıştırıcının sonuçlarından sorumlu tutulabiliyorsanız, size belirsiz bir sorun vermeleri adil değildir.

... İyi bulduğum gramerden emin değilim (makul herhangi bir "iyi" ölçüsüyle).

Durumunuzda "iyi" demek "pozitifleri ayrıştırır ve negatifleri reddeder" anlamına gelecektir. Girdi dosyanızın sözdiziminin başka herhangi bir resmi belirtimi olmadan, örnekler tek test durumunuzdur ve bundan daha iyisini yapamazsınız. Ayağınızı yere koyabilir ve sadece olumlu örneklerin iyi olduğunu ve başka bir şeyi reddettiğini söyleyebilirsiniz, ancak bu muhtemelen yapmaya çalıştığınız şeyin ruhunda değildir.

Daha akılcı koşullar altında, bir dilbilgisini test etmek başka bir şeyi test etmek gibidir: terminallerin (ve bir lexer tarafından üretiliyorsa terminallerin) tüm varyantlarını uygulamak için yeterli test senaryosu bulmanız gerekir.


Örnek Problem

Aşağıdaki kurallarla tanımlanan bir liste içeren metin dosyalarını ayrıştıracak bir dilbilgisi yazın :

  • Bir liste sıfır veya daha fazla oluşur şeyler .
  • Bir şey bir tanımlayıcı , bir açık parantez, bir öğe listesi ve bir kapanış parantezinden oluşur.
  • Bir _item_list_ sıfır veya daha fazla oluşur öğeler .
  • Bir öğe , bir tanımlayıcının , eşittir işaretinin, başka bir tanımlayıcının ve noktalı virgülden oluşur.
  • Bir tanımlayıcı AZ, az, 0-9 ya da alt çizgi bir ya da daha fazlasının bir dizisidir.
  • Boşluk yoksayılır.

Giriş örneği (tümü geçerli):

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

2
Ve BNF'nin kendisini değil, "Backus-Naur'un bazı varyantlarını" kullandığınızdan emin olun. BNF bir dilbilgisini ifade edebilir, ancak listeler gibi çok yaygın kavramları olması gerekenden çok daha karmaşık hale getirir. EBNF gibi bu konularda iyileştirilen çeşitli geliştirilmiş sürümler vardır.
Mason Wheeler

7

Macneil ve Blrfl'in cevapları harika. Sadece sürecin başlangıcı hakkında bazı yorumlar eklemek istiyorum.

Bir sözdizimi bir temsil için sadece bir yoludur programı . Dolayısıyla, dilinizin sözdizimi şu soruya verdiğiniz yanıtla belirlenmelidir: Program nedir?

Bir programın sınıflar topluluğu olduğunu söyleyebilirsiniz. Tamam, bu bize

program ::= class*

başlangıç ​​noktası olarak. Veya yazmak zorunda kalabilirsiniz

program ::= ε
         |  class program

Şimdi, sınıf nedir? Bir ismi var; isteğe bağlı bir üst sınıf spesifikasyonu; ve bir grup kurucu, yöntem ve alan bildirimi. Ayrıca, bir sınıfı tek (açık) bir birimde gruplamanın bir yoluna ihtiyacınız vardır ve bu da kullanılabilirlik konusunda bazı tavizler içermelidir (örneğin, ayrılmış sözcükle etiketleyin class). Tamam:

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

Seçebileceğiniz bir gösterim ("somut sözdizimi"). Ya da buna kolayca karar verebilirsiniz:

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

veya

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

Muhtemelen bu kararı üstü kapalı olarak verdiniz, özellikle de örnekleriniz varsa, ama sadece noktayı güçlendirmek istiyorum: Sözdiziminin yapısı, temsil ettiği programların yapısı tarafından belirlenir. Macneil'in cevabındaki "önemsiz gramerleri" aşan şey budur. Yine de örnek programlar hala çok önemlidir. İki amaca hizmet ederler. İlk olarak, soyut seviyede bir programın ne olduğunu anlamanıza yardımcı olurlar. İkincisi, dilinizin yapısını temsil etmek için hangi somut sözdizimini kullanmanız gerektiğine karar vermenize yardımcı olurlar.

Yapıyı indirdikten sonra, geri dönüp boşluklara ve yorumlara izin verme, belirsizlikleri düzeltme vb. Gibi konularla başa çıkmalısınız. Bunlar önemlidir, ancak genel tasarıma ikincildirler ve kullandığınız teknolojiyi ayrıştırma.

Son olarak, dilinizle ilgili her şeyi dilbilgisinde temsil etmeye çalışmayın. Örneğin, belirli erişilemeyen kod türlerini (örneğin, returnJava'da olduğu gibi a'dan sonraki bir ifade) yasaklamak isteyebilirsiniz . Muhtemelen bunu dilbilgisine sıkıştırmaya çalışmamalısınız, çünkü ya şeyleri özleyeceksiniz (whoops, returnparantez içinde ise ya da bir ififadenin her iki dalı da geri dönerse?) Ya da dilbilginizi çok karmaşık hale getireceksiniz yönetmek. Bu bir var içeriğe duyarlı kısıt; ayrı bir geçiş olarak yazın. Bağlama duyarlı bir kısıtlamanın çok yaygın bir başka örneği bir tür sistemidir. 1 + "a"Yeterince sıkı çalıştıysanız, dilbilgisi gibi ifadeleri reddedebilirsiniz , ancak reddedemezsiniz 1 + x(burada xstring dizesi vardır). Yanidilbilgisinde yarı pişmiş kısıtlamalardan kaçının ve bunları ayrı bir geçiş olarak doğru şekilde yapın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.