Bağlamdan bağımsız gramerleri, çoğunlukla kısa parçacıkları ayrıştırma


20

Kullanıcı tanımlı etki alanına özgü dilleri ayrıştırmak istiyorum. Bu diller genellikle matematiksel gösterimlere yakındır (doğal bir dili ayrıştırmıyorum). Kullanıcılar DSL'lerini aşağıdaki gibi bir BNF notasyonunda tanımlar:

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

Like 1 + ( 2 * 3 )girişi kabul edilirken, like girişi 1 +yanlış olarak reddedilmeli ve like girişi 1 + 2 * 3belirsiz olarak reddedilmelidir.

Buradaki merkezi zorluk, belirsiz gramerlerle kullanıcı dostu bir şekilde baş etmektir. Dilbilgisinin açık olmasını kısıtlamak bir seçenek değildir: dil bu şekilde olur - fikir şu ki, belirsizlikleri önlemek için gerekli olmadığında parantezleri atlamayı tercih ederler. Bir ifade belirsiz olmadığı sürece, onu ayrıştırmam gerekir ve eğer değilse, reddetmem gerekir.

Ayrıştırıcım, bağlam içermeyen herhangi bir dilbilgisi, hatta belirsiz olanlar üzerinde çalışmalı ve tüm açık girdileri kabul etmelidir. Kabul edilen tüm girdiler için ayrıştırma ağacına ihtiyacım var. Geçersiz veya belirsiz giriş için ideal olarak iyi hata mesajları istiyorum, ancak başlamak için alabileceğimi alacağım.

Genellikle ayrıştırıcıyı nispeten kısa girdilerle ve arada sırada daha uzun girdilerle çağıracağım. Bu nedenle asimptotik olarak daha hızlı algoritma en iyi seçim olmayabilir. 20 sembol uzunluğundan daha az% 80, 20 ila 50 sembol arasında% 19 ve% 1 nadir daha uzun giriş dağılımı için optimizasyon yapmak istiyorum. Geçersiz girişlerin hızı büyük bir endişe kaynağı değildir. Ayrıca, DSL'de her 1000 ila 100000 giriş arasında bir değişiklik bekliyorum; Dilbilgimi ön işleme almak için birkaç saniye harcıyorum, birkaç dakika değil.

Tipik girdi boyutlarım göz önüne alındığında hangi ayrıştırma algoritmalarını araştırmalıyım? Hata bildirimi seçimimde bir faktör olmalı mı, yoksa net olmayan girdileri ayrıştırmaya odaklanmalı ve muhtemelen hata geribildirimi sağlamak için tamamen ayrı, daha yavaş bir ayrıştırıcı çalıştırmalı mıyım?

(İhtiyacım olan projede (bir süre önce), uygulanması çok zor olmayan ve girdi boyutlarım için yeterince çalışan ancak çok hoş hatalar oluşturmayan CYK kullandım .)


Özellikle iyi hata raporlarına ulaşmak zor görünüyor. Belirsiz gramerler durumunda kabul edilen bir girdiye yol açan birden fazla yerel değişikliğiniz olabilir.
Raphael

Az önce aşağıda cevap verdim. Zaten iyi cevaplanmış eski bir sorunun modifikasyonuna cevap vermek biraz gariptir. Açıkçası benzer bir şekilde cevap vermemem gerekiyor, ancak kullanıcılar her iki yanıtı da aynı soruyu yanıtlıyormuş gibi okuyacaklar.
babou

Bir kullanıcı yazarsa, belirsiz bir giriş için gerçekten bir hata mesajı bekler misiniz x+y+z?
babou

@babou Soruyu değiştirmedim, yalnızca yorumlarda istenen açıklamaları ekledim (şimdi silindi). Burada verilen küçük dilbilgisi için, ben bir ilişki belirtmedim +, bu yüzden x+y+zgerçekten de belirsiz.
Gilles 'SO- kötü olmayı bırak'

Parantez arasında olsa bile, son cümleniz eklendi. Şöyle diyorsunuz: Sonunda CYK ile yaptım, ancak bazı nedenlerden dolayı artık yeterli değil. Ve kesin nedenlerin ne olabileceğini merak ediyorum ... Şimdi , sizin probleminiz ve kullandığınız çözüm ile en fazla deneyimi olan kişisiniz, bu yüzden daha fazla cevap verilecekse sizden daha fazla bilgi bekleyebilirsiniz.
babou

Yanıtlar:


19

Muhtemelen ihtiyaçlarınız için ideal algoritma Generalized LL ayrıştırma veya GLL'dir. Bu çok yeni bir algoritmadır (makale 2010'da yayınlanmıştır). Bir bakıma, grafik yapılandırılmış bir yığınla (GSS) artırılmış ve LL (1) ileriye dönük olarak kullanılan Earley algoritmasıdır.

Algoritma, eski LL (1) ile oldukça benzerdir, ancak LL (1) değilse dilbilgisini reddetmez: sadece olası tüm LL (1) ayrıştırmalarını dener. Ayrıştırmadaki her nokta için yönlendirilmiş bir grafik kullanır, yani daha önce ele alınan bir ayrıştırma durumu ile karşılaşılırsa, bu iki köşeyi birleştirir. Bu, LL'den farklı olarak, sol yinelemeli gramerler için bile uygun hale getirir. İç çalışmalarıyla ilgili kesin ayrıntılar için kağıdı okuyun (etiket çorbası biraz azim gerektirse de oldukça okunabilir bir kağıttır).

Algoritmanın, (genel olarak bildiğim) diğer genel ayrıştırma algoritmalarına göre ihtiyaçlarınızla ilgili bir dizi belirgin avantajı vardır. İlk olarak, uygulama çok kolaydır: Bence sadece Earley'nin uygulanması daha kolaydır. İkincisi, performans oldukça iyidir: aslında, LL (1) gramerlerinde LL (1) kadar hızlı olur. Üçüncüsü, ayrıştırmayı kurtarmak oldukça kolaydır ve birden fazla ayrıştırma olup olmadığını kontrol etmek de mümkündür.

GLL'nin temel avantajı, LL (1) 'e dayanması ve bu nedenle, uygulama sırasında, gramer tasarlarken ve girdileri ayrıştırırken anlaşılması ve hata ayıklamasının çok kolay olmasıdır. Ayrıca, hata işlemeyi de kolaylaştırır: olası ayrıştırmaların nerede olduğunu ve nasıl devam edebileceğini tam olarak bilirsiniz. Olası ayrıştırmaları hatanın noktasında ve diyelim ki ayrıştırmaların sarmal olduğu son 3 noktayı kolayca verebilirsiniz. Bunun yerine hatadan kurtulmayı deneyebilir ve en uzaktaki ayrıştırmanın üzerinde çalıştığı üretimi bu ayrıştırma için 'tam' olarak işaretleyebilir ve ayrıştırma işleminin bundan sonra devam edip edemeyeceğini görebilirsiniz (birisinin parantez unuttuğunu söyleyin). Bunu, en uzak olan 5 bölüm için bile yapabilirsiniz.

Algoritmanın tek dezavantajı, yeni olması, yani hazır bir uygulama bulunmadığı anlamına geliyor. Bu sizin için bir sorun olmayabilir - algoritmayı kendim uyguladım ve bunu yapmak oldukça kolaydı.


Yeni bir şeyler öğrenmek güzel. Buna ihtiyacım olduğunda (birkaç yıl önce, bir gün canlandırmak istediğim bir projede), büyük ölçüde bulduğum ilk algoritma olduğu için CYK'yi kullandım. GLL belirsiz girişleri nasıl işler? Makale bunu tartışmıyor gibi görünüyor, ama ben sadece gözden kaçırdım.
Gilles 'SO- kötü olmayı bırak'

@Gilles: bir grafik yapılı yığın oluşturur ve tüm (potansiyel olarak üstel olarak birçok) ayrıştırmalar bu grafikte GLR'nin nasıl çalıştığına benzer şekilde kompakt bir şekilde temsil edilir. Doğru hatırlarsam, cstheory.stackexchange.com/questions/7374/… 'de bahsedilen makale bununla ilgilenir.
Alex ten Brink

@Gilles Bu 2010 ayrıştırıcısı dilbilgisinden elle programlanmış gibi görünüyor, birden fazla diliniz varsa veya dili sık sık değiştiriyorsanız çok yeterli değil. Seçilen herhangi bir stratejiyi (LL, LR veya diğerleri) takip ederek genel ayrıştırıcının gramerinden otomatik üretme ve tüm ayrıştırmalardan oluşan bir orman üretme teknikleri yaklaşık 40 yıldır bilinmektedir. Ancak, ayrışmayı temsil eden grafiğin karmaşıklığı ve organizasyonu ile ilgili gizli konular vardır. Ayrıştırma sayısı üstel olandan daha kötü olabilir: sonsuz. Hata kurtarma daha sistematik, ayrıştırıcıdan bağımsız teknikler kullanabilir.
babou

GLL, ANTLR'de bulunan LL (*) ile nasıl ilişkilidir?
Raphael

6

Şirketim (Semantik Tasarımlar) OP'nin hem etki alanına özgü dilleri ayrıştırmak hem de "klasik" programlama dillerini ayrıştırmak için DMS Yazılım Yeniden Yapılandırma Araç Seti'mizi tam olarak yapmak için GLR ayrıştırıcılarını çok başarılı bir şekilde kullandı. Bu, büyük ölçekli program yeniden yapılandırması / tersine mühendislik / ileri kod üretimi için kullanılan kaynaktan kaynağa program dönüşümlerini destekler . Bu, sözdizimi hatalarının oldukça pratik bir şekilde otomatik olarak onarılmasını içerir. GLR'yi temel olarak ve diğer bazı değişiklikleri (semantik tahminler, sadece jeton girişi yerine jeton seti girişi, ...) kullanarak 40 dilde ayrıştırıcılar oluşturmayı başardık.

Tam dil örneklerini ayrıştırma yeteneği kadar önemli olan GLR'nin, kaynaktan kaynağa yeniden yazma kurallarının ayrıştırılmasında da son derece yararlı olduğu kanıtlanmıştır . Bunlar, tam bir programdan çok daha az içeriğe sahip program parçalarıdır ve bu nedenle genellikle daha fazla belirsizliğe sahiptir. Kuralları ayrıştırma sırasında / sonrasında bu belirsizliklerin giderilmesine yardımcı olması için özel ek açıklamalar kullanırız (örneğin, bir ifadenin belirli bir dilbilgisi terminale karşılık gelmediğinde ısrar etmek). GLR ayrıştırma makinelerini ve çevresindeki araçları düzenleyerek, dili için bir ayrıştırıcıya sahip olduğumuzda "ücretsiz" için yeniden yazma kuralları için ayrıştırıcılar alırız. DMS motorunda, daha sonra istenen kod değişikliklerini gerçekleştirmek için bu kuralı uygulamak için kullanılabilen yerleşik bir yeniden yazma kuralı uygulayıcısı vardır.

Muhtemelen en çarpıcı sonucumuz, tüm belirsizliğe rağmen, bağlamsız bir dilbilgisi temel alarak tam C ++ 14'ü ayrıştırma yeteneğidir . Tüm klasik C ++ derleyicilerinin (GCC, Clang) bunu yapma ve elle yazılmış ayrıştırıcıları kullanma yeteneğinden vazgeçtiğini (IMHO'nun bakımını daha zor hale getirdiğini, ancak sonra benim sorunum olmadığını) not ediyorum. Bu makineyi büyük C ++ sistemlerinin mimarisinde büyük değişiklikler yapmak için kullandık.

Performans açısından GLR ayrıştırıcılarımız oldukça hızlıdır: saniyede on binlerce satır. Bu, son teknolojinin oldukça altındadır, ancak bunu optimize etmek için ciddi bir girişimde bulunmadık ve bazı darboğazlar karakter akışı işlemede (tam Unicode). Bu ayrıştırıcıları oluşturmak için, bir LR (1) ayrıştırıcı üreticisine oldukça yakın bir şey kullanarak bağlamsız gramerleri önceden işleriz; bu normalde C ++ büyüklüğünde büyük gramerlerde on saniyede modern bir iş istasyonunda çalışır. Şaşırtıcı bir şekilde, modern COBOL ve C ++ gibi çok karmaşık diller için, sözcüklerin oluşturulması yaklaşık bir dakika sürüyor; Unicode üzerinde tanımlanan DFA'lardan bazıları oldukça tüylüdür. Ben sadece Ruby (inanılmaz regexps için tam bir subgrammar ile) bir parmak egzersiz olarak yaptım; DMS, lexer ve dilbilgisini yaklaşık 8 saniye içinde birlikte işleyebilir.


@Raphael: "Büyük değişiklikler" bağlantısı, C ++ mimarisinin yeniden yapılandırılmasıyla ilgili birkaç tanesi, biri DMS motorunun kendisinde (oldukça eski ancak temelleri iyi açıklar) ve diğeri de DMS için orijinal motivasyon olan tasarım yakalama ve yeniden kullanımın egzotik konusu (maalesef hala maalesef DMS'nin oldukça faydalı olduğu ortaya çıktı).
Ira Baxter

1

Belirsiz cümleleri (belirsiz bir dilbilgisine göre) ayrıştırabilen birçok genel bağlamsız ayrıştırıcı vardır. Özellikle dinamik programlama veya grafik ayrıştırıcıları olmak üzere çeşitli isimler altında gelirler. En iyi bilinen ve en basit olanı, muhtemelen kullandığınız CYK ayrıştırıcısıdır. Birden fazla ayrışmayı ele almanız gerektiğinden bu genelliğe ihtiyaç vardır ve bir belirsizliğe karşı olup olmadığınızı sonuna kadar bilemeyebilirsiniz.

Söylediklerinden, CYK'nin o kadar da kötü bir seçim olmadığını düşünürdüm. Muhtemelen öngörülebilirlik (LL veya LR) ekleyerek kazanacağınız çok şey yoktur ve aslında ayrımcılıktan ziyade birleştirilmesi gereken hesaplamaları ayırt ederek bir maliyeti olabilir (özellikle LR durumunda). Üretilen ayrıştırma ormanının büyüklüğü de buna karşılık gelen bir maliyete sahip olabilirler (bu, belirsizlik hatalarında rol oynayabilir). Aslında, daha karmaşık algoritmaların yeterliliğini resmi olarak nasıl karşılaştıracağımdan emin olmasam da, CYK'nin iyi bir hesaplama paylaşımı sağladığını biliyorum.

Şimdi, belirsiz CF gramerleri için sadece kesin olmayan girdileri kabul etmesi gereken çok fazla literatür olduğuna inanmıyorum. Muhtemelen teknik belgeler ve hatta programlama dilleri için bile sözdizimsel belirsizliğin, başka yollarla çözülebildiği sürece kabul edilebilir olması (örneğin ADA ifadelerindeki belirsizlik) olduğunu hatırlamıyorum.

Aslında neye sahip olduğunuza bağlı kalmak yerine neden algoritmanızı değiştirmek istediğinizi merak ediyorum. Bu, size en iyi nasıl bir değişikliğin yardımcı olabileceğini anlamama yardımcı olabilir. Bu bir hız sorunu mu, ayrıştırmaların temsili mi, yoksa hata algılama ve kurtarma mı?

Birden fazla ayrışmayı temsil etmenin en iyi yolu, yalnızca girdinizi oluşturan bağlam içermeyen bir dilbilgisi olan, ancak DSL dilbilgisi ile tamamen aynı ayrıştırma ağaçlarıyla paylaşılan bir ormandır. Bu, anlaşılmasını ve işlenmesini çok kolaylaştırır. Daha fazla ayrıntı için, dil sitesinde verdiğim bu cevaba bakmanızı öneririm . Bir ayrıştırma ormanı almakla ilgilenmediğinizi anlıyorum, ancak ayrıştırma ormanının uygun bir şekilde temsil edilmesi, belirsizliğin probleninin ne olduğu hakkında daha iyi mesajlar vermenize yardımcı olabilir. Bunu yapmak istiyorsanız, bazı durumlarda (ilişkilendirilebilirlik) belirsizliğin önemli olmadığına karar vermenize de yardımcı olabilir.

DSL gramerinizin işlem süresi kısıtlamalarından bahsediyorsunuz, ancak boyutuyla ilgili hiçbir ipucu vermiyorsunuz (bu, yaptığınız rakamlarla cevaplayabileceğim anlamına gelmez).

Bazı hata işlemleri bu genel CF algoritmalarına basit yollarla entegre edilebilir. Ancak, ne tür bir hata işlemenin daha olumlu olmasını beklediğinizi anlamam gerekir. Bazı örnekleriniz var mı?

Daha fazlasını söylemek biraz rahatım çünkü motivasyonlarınız ve kısıtlamalarınızın gerçekte ne olduğunu anlamıyorum. Söylediklerinize dayanarak, CYK'ye bağlı kalacağım (ve diğer algoritmaları ve bazı özelliklerini biliyorum).

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.