Parser Combinator ne zaman kullanılır? Ayrıştırıcı Jeneratör ne zaman kullanılır?


59

Kendi programlama dilimi oluşturmak isteyen son zamanlarda ortakların dünyasına derin bir dalış yaptım.

Ancak, ayrıştırıcı yazma için iki farklı yaklaşımın var olduğunu öğrendim: Ayrıştırıcı Jeneratörler ve Ayrıştırıcı Birleştiriciler.

İlginçtir ki, hangi yaklaşımın daha iyi olduğu konusunda hangi kaynakları açıklayabileceğimi; Aksine, birçok kaynak (ve kişi) Ben tabi sadece açıklayan diğer yaklaşım bilmiyordum hakkında sorgulanan onların olarak yaklaşımını yaklaşımı ve diğer tüm söz değil:

  • Ünlü Ejderha kitap lexing / tarama girer ve (f) lex bahseder, fakat hiç Ayrıştırıcı combinators bahsetmiyor.
  • Dil Uygulama Kalıpları , Java'da yerleşik olan ANTLR Ayrıştırıcı Jeneratörüne büyük ölçüde dayanır ve Ayrıştırıcı Birleştiricilerden hiç bahsetmez.
  • Haskell'de bir Parser Combinator olan Parsec'deki Parsec öğreticisine giriş , Parser Jeneratörlerinden hiç bahsetmiyor.
  • Boost :: ruhu , en iyi bilinen C ++ Parser Combinator, Parser Jeneratörlerinden hiç bahsetmiyor.
  • Harika açıklayıcı blog yazısı Parser Combinators , Parser Generator'den hiç bahsetmedi.

Basit Genel Bakış:

Ayrıştırıcı Jeneratör

Bir Ayrıştırıcı Jeneratörü, bir DSL'de yazılmış, bir Genişletilmiş Backus-Naur formunun lehçesi olan bir dosyayı alır ve daha sonra (derlendiğinde) bu DSL'de açıklanan giriş dili için bir ayrıştırıcı haline gelebilecek kaynak koduna dönüştürür.

Bu, derleme işleminin iki ayrı adımda gerçekleştirildiği anlamına gelir. İlginçtir, Ayrıştırıcı Jeneratörlerin kendileri de derleyicilerdir (ve çoğu aslında kendi kendini barındıran ).

Ayrıştırıcı Birleştirici

Bir Ayrıştırıcı Birleştiricisi , tümü parametre olarak bir giriş alan ve ayrıştırırsa , bu girişin ilk karakterlerini çıkarmaya çalışan ayrıştırıcılar adı verilen basit işlevleri tanımlar . Bir tuple döndürmek (result, rest_of_input), resultboş (örneğin olabilir nilveya Nothingçözümleyici bu girişten şey ayrıştıramadı ise). Bir örnek digitayrıştırıcı olabilir. Diğer ayrıştırıcılar, elbette, ayrıştırıcıları, bunları birleştirmek için ilk argümanları (son argüman hala giriş dizesini kalır) olarak alabilir : örn. many1, Başka bir ayrıştırıcıyı mümkün olduğu kadar çok kez eşleştirmeye çalışır (ancak en az bir kez veya kendisi başarısız olur).

Tabii ki şimdi elbette birleştirebilir (oluşturabilir) digitve many1yeni bir ayrıştırıcı oluşturabilirsiniz integer.

Ayrıca, choiceher birini sırasıyla denemek üzere ayrıştırıcıların listesini alan daha yüksek düzeyde bir ayrıştırıcı yazılabilir.

Bu şekilde, çok karmaşık sözlükler / ayrıştırıcılar oluşturulabilir. Operatör aşırı yüklemesini destekleyen dillerde, doğrudan hedef dilde yazılsa bile, bu aynı zamanda EBNF'ye çok benziyor (ve istediğiniz hedef dilin tüm özelliklerini kullanabilirsiniz).

Basit Farklar

Dil:

  • Ayrıştırma Jeneratörleri, EBNF-ish DSL ve bu ifadelerin eşleştikleri zaman oluşturmaları gereken kodun bir kombinasyonu halinde yazılır.
  • Ayrıştırıcı Birleştiriciler, hedef dile doğrudan yazılır.

Lexing / Ayrıştırma:

  • Ayrıştırma Jeneratörleri, 'lexer' (ne bir değerle uğraştığımızı göstermek için etiketlenebilecek belirteçlere dizgiyi böler) ve 'ayrıştırıcı' (belirteçlerin çıktı listesini alır.) ve bunları bir Soyut Sözdizimi Ağacı oluşturarak birleştirmeye çalışır).
  • Ayrıştırıcı Birleştiriciler bu ayrıma sahip değildir / buna ihtiyaç duymazlar; genellikle, basit ayrıştırıcılar 'lexer'ın çalışmasını gerçekleştirir ve daha üst düzey ayrıştırıcılar, bu tür olanları, hangi tür AST düğümünün oluşturulacağına karar vermeye karar verir.

Soru

Ancak, bu farklılıklar göz önüne alınsa bile (ve bu farklılıkların listesi büyük olasılıkla tam olmaktan uzaktır!), Ne zaman kullanılacağı konusunda eğitimli bir seçim yapamam . Bu farklılıkların sonuçlarının / sonuçlarının ne olduğunu göremiyorum.

Hangi sorun özellikleri bir sorunun Ayrıştırıcı Jeneratör kullanılarak çözüleceğini gösterir? Hangi problem özellikleri bir sorunun ve bir Ayrıştırıcı Birleştirici kullanılarak daha iyi çözüleceğini gösterir?


4
Ayrıştırıcıları bahsetmediğiniz en az iki yol daha vardır: ayrıştırıcı tercümanları (ayrıştırıcı dilini örneğin C veya Java'ya derleme dışında, ayrıştırıcı üreticilere benzer şekilde) ve yalnızca elle ayrıştırıcı. Ayrıştırıcıyı el ile yazmak, birçok modern üretime hazır endüstriyel dayanım dili uygulaması (örneğin, GCC, Clang javac, Scala) için tercih edilen uygulama şeklidir . Size iyi hata mesajları üretme konusunda yardımcı olan iç ayrıştırıcı durumu üzerinde en fazla kontrolü verir (ki bu son yıllarda…
Jörg W Mittag

3
… Dil uygulayıcıları için çok yüksek bir öncelik haline geldi). Ayrıca, mevcut birçok ayrıştırıcı jeneratör / tercüman / birleştirici modern dil uygulamalarının yerine getirmesi gereken çok çeşitli taleplerle başa çıkmak için tasarlanmamıştır. Örneğin birçok modern dil uygulaması, toplu derleme, IDE arka plan derlemesi, sözdizimi vurgulama, otomatik yeniden düzenleme, akıllı kod tamamlama, otomatik dokümantasyon oluşturma, otomatik diyagram oluşturma vb. İçin aynı kod parçasını kullanır. . Mevcut birçok ayrıştırıcı…
Jörg W Mittag

1
… Çerçeveler bununla başa çıkabilecek kadar esnek değil. Ayrıca, EBNF'ye dayanmayan ayrıştırıcı çerçevelerin olduğunu unutmayın. Örneğin İfade Dilbilgileri Ayrıştırma için paket ayrıştırıcıları .
Jörg W Mittag

2
Bence bu derleme yapmaya çalıştığınız dile bağlı. Ne tür (LR, ...)?
qwerty_so

1
Yukarıdaki varsayımınız, genellikle sadece lexer / LR ayrıştırıcı birleşimi ile derlenen BNF'ye dayanmaktadır. Ancak, dillerin mutlaka LR gramerlerine dayanması gerekmez. Öyleyse, hangisini derlemeyi planlıyorsun?
qwerty_so

Yanıtlar:


59

Bu ayrı teknolojilerin neden var olduğunu ve güçlü ve zayıf yönlerinin neler olduğunu daha iyi anlamak için, son birkaç gündür çok araştırma yaptım.

Halihazırda varolan cevapların bazıları farklılıklarının bir kısmını ima ediyordu, ancak tam resmi vermediler ve bir nevi fikirde görünüyorlardı, bu yüzden bu cevap yazıldı.

Bu açıklama uzun, ama önemli. Benimle ayı (eğer sabırsız iseniz Veya, bir akış şeması görmek için sonuna gidin).


Ayrıştırıcı Kombinatorler ve Ayrıştırıcı Jeneratörler arasındaki farkları anlamak için öncelikle var olan çeşitli ayrıştırma türleri arasındaki farkı anlamak gerekir.

ayrıştırma

Ayrıştırma, bir resmi dilbilgisine göre bir dizi sembolün analiz edilmesi işlemidir. (Bilişim Bilimi,), ayrıştırma, bir bilgisayarın bir dilde yazılmış metni anlamalarına izin vermek için kullanılır, genellikle yazılı metni temsil eden bir ayrıştırma ağacı oluşturur , farklı yazılı bölümlerin anlamlarını ağacın her düğümünde saklar. Bu ayrıştırma ağacı, daha sonra başka bir dile (birçok derleyicide kullanılan) çevirme, yazılı talimatları doğrudan bir şekilde yorumlama (SQL, HTML), Linters gibi araçların işlerini yapmalarına izin verme gibi çeşitli amaçlar için kullanılabilir. , vb. Bazen bir ayrıştırma ağacı açıkça değildir.Oluşturulduktan sonra ağaçtaki her düğüm türünde yapılması gereken eylem doğrudan yürütülür. Bu, verimliliği artırıyor, ancak sualtında hala gizli bir ayrıştırma ağacı var.

Ayrıştırma işlemsel olarak zor bir sorundur. Bu konuda elli yılı aşkın bir süredir araştırma yapıldı, ancak hala öğrenilmesi gereken çok şey var.

Kabaca konuşursak, bir bilgisayarın girişi ayrıştırmasına izin veren dört genel algoritma vardır:

  • LL ayrıştırma. (Bağlamsız, yukarıdan aşağıya ayrıştırma.)
  • LR ayrıştırma. (Bağlamsız, aşağıdan yukarıya ayrıştırma.)
  • PEG + Packrat ayrıştırma.
  • Earley Ayrıştırma.

Bu ayrıştırma türlerinin çok genel ve teorik tanımlamalar olduğunu unutmayın. Bu algoritmaların her birini farklı işlemlerle fiziksel makinelere uygulamak için birçok yol vardır.

LL ve LR sadece Bağlamsız gramerlere bakabilir (yani; yazılan belirteçlerin etrafındaki içerik nasıl kullanıldığını anlamak için önemli değildir).

PEG / Packrat ayrıştırma ve Earley ayrıştırma çok daha az kullanılır: Earley ayrıştırma çok daha fazla gramer işleyebileceği için güzeldir (zorunlu olarak ücretsiz olmayanlar dahil) ancak daha az verimlidir (ejderha tarafından iddia edildiği gibi) kitap (bölüm 4.1.1); bu iddiaların hala doğru olup olmadığından emin değilim). Ayrıştırma İfadesi Dilbilgisi + Paket-ayrıştırma , göreceli olarak verimli olan ve hem LL hem de LR'den daha fazla gramer işleyebilen bir yöntemdir, ancak aşağıda hızlı bir şekilde değinildiği gibi belirsizlikleri gizler.

LL (Soldan sağa, Soldan türetme)

Bu muhtemelen ayrıştırmayı düşünmenin en doğal yoludur. Buradaki düşünce, giriş dizgisindeki bir sonraki belirtecine bakmak ve daha sonra, bir ağaç yapısını oluşturmak için muhtemel çoklu tekrarlamalı çağrıların hangisinin yapılması gerektiğine karar vermektir.

Bu ağaç 'yukarıdan aşağıya' inşa edilmiştir, bu da ağacın kökünden başlayacağımız anlamına gelir ve dilbilgisi kurallarını giriş dizgisi boyunca hareket ettiğimiz şekilde hareket ettiririz. Ayrıca okunmakta olan 'infix' token akışı için bir 'postfix' eşdeğerinin oluşturulması olarak görülebilir.

LL tarzı ayrıştırma gerçekleştiren ayrıştırıcılar, belirtilen orijinal dilbilgisine çok benzemek için yazılabilir. Bu onları anlamak, hata ayıklamak ve geliştirmek nispeten kolay hale getirir. Klasik Ayrıştırıcı Birleştiriciler, LL tarzı bir ayrıştırıcı oluşturmak için bir araya getirilebilecek olan “lego parçalarından” başka bir şey değildir.

LR (Soldan sağa, en sağdan türetme)

LR ayrıştırması diğer yoldan aşağıya doğru hareket eder: Her adımda, yığındaki üst eleman (lar) gramerde daha üst bir seviyeye indirgenip düşürülmeyeceklerini görmek için dilbilgisi listesiyle karşılaştırılır . Değilse, girdi akışından sonraki belirteç kaydırılır ve yığının üstüne yerleştirilir.

Sonunda, gramerimizdeki başlangıç ​​kuralını temsil eden istif üzerinde tek bir düğümle sonuçlanırsak, bir program doğrudur.

lookahead

Bu iki sistemden birinde, bazen hangi seçimi yapacağınıza karar vermeden önce girdilerden daha fazla simge atmanız gerekir. Bu (0), (1), (k)veya (*)böyle bu iki genel algoritmalar, isimlerinin sonra göreceği -syntax LR(1) veya LL(k). kgenellikle 'dilbilgisi ihtiyaç duyduğunuz kadar' *anlamına gelirken, genellikle 'bu ayrıştırıcı geri izleme işlemini gerçekleştirir' anlamına gelir; bu uygulamanın uygulanması daha güçlü / kolaydır, ancak yalnızca ayrıştırmaya devam edebilen bir ayrıştırıcıdan çok daha yüksek bellek ve zaman kullanımına sahiptir doğrusal.

LR stili ayrıştırıcıların, 'ileriye bakmaya' karar verebilecekleri durumda, yığında zaten çok sayıda belirteç bulunduğunu, bu nedenle gönderilecek daha fazla bilgiye sahip olduklarını unutmayın. Bu, aynı dilbilgisi için bir LL tarzı ayrıştırıcıdan daha az 'bakıma' ihtiyaç duymaları anlamına gelir.

LL - LR: Belirsizlik

Yukarıdaki iki tanımı okurken, LL tarzı ayrıştırmanın çok daha doğal göründüğü için LR tarzı ayrıştırmanın neden var olduğunu merak edebilirsiniz.

Bununla birlikte, LL stili ayrıştırmada bir sorun var: Left Recursion .

Böyle bir gramer yazmak çok doğaldır:

expr ::= expr '+' expr | term term ::= integer | float

Ancak, LL tarzı bir çözümleyici bu dilbilgisini ayrıştırırken sonsuz bir özyinelemeli döngüde sıkışıp kalacak: exprKuralın en soldaki olasılığını denerken, herhangi bir girdi kullanmadan tekrar bu kurala geri çekilir.

Bu sorunu çözmenin yolları var. En basit olanı, gramerinizi yeniden yazmaktır; böylece bu tür bir özyineleme artık gerçekleşmez:

expr ::= term expr_rest expr_rest ::= '+' expr | ϵ term ::= integer | float (Burada ϵ 'boş dize' anlamına gelir)

Bu dilbilgisi şimdi doğru özyinelemeli. Hemen okumak daha zor olduğunu unutmayın.

Uygulamada, sol özyineleme, aradaki birçok adımla dolaylı olarak gerçekleşebilir . Bu dikkat edilmesi zor bir problemdir. Ancak bunu çözmeye çalışmak, gramerinizi okumayı zorlaştırır.

Dragon Kitabın 2.5 Bölümünde belirtildiği gibi:

Bir anlaşmazlık var gibi görünüyor: bir yandan çeviriyi kolaylaştıran dilbilgisine ihtiyacımız var, diğer taraftan ayrıştırmayı kolaylaştıran önemli ölçüde farklı bir dilbilgisine ihtiyacımız var. Çözüm, kolay çeviri için gramer ile başlamak ve ayrıştırmayı kolaylaştırmak için dikkatlice dönüştürmektir. Sol özyinelemeyi elimine ederek, öngörülü özyinelemeli bir tercümanda kullanıma uygun bir gramer elde edebiliriz.

LR stili ayrıştırıcılar, ağacı aşağıdan yukarıya doğru inşa ederken bu sol-özyinelemeyle ilgili bir problemi yoktur. Ancak , (genellikle bir şekilde uygulanan bir LR tarzı ayrıştırıcı yukarıdaki gibi bir gramer zihinsel çeviri Sonlu-Devlet Automaton )
+ (ve hataya açık) sıklıkta devletlerin yüzlerce veya binlerce vardır yapmak çok zordur durum geçişlerini dikkate almak. Bu nedenle LR stili ayrıştırıcılar genellikle 'derleyici derleyici' olarak da bilinen bir Ayrıştırıcı Jeneratör tarafından oluşturulur .

Belirsizlikler nasıl çözülür?

Yukarıdaki Sol yinelemeyle ilgili belirsizlikleri çözmek için iki yöntem gördük: 1) sözdizimini yeniden yazın 2) bir LR ayrıştırıcısı kullanın.

Ancak çözülmesi zor olan başka tür belirsizlikler var: Aynı anda iki farklı kural aynı şekilde uygulanabilirse ne olur?

Bazı yaygın örnekler:

Hem LL stili hem de LR stili ayrıştırıcılarında bunlarla ilgili sorunlar var. Aritmetik ifadelerin ayrıştırılmasıyla ilgili problemler, operatör önceliği belirtilerek çözülebilir. Benzer bir şekilde, Dangling Else gibi diğer sorunlar, bir öncelikli davranış seçerek ve buna bağlı kalarak çözülebilir. (C / C ++ 'da, örneğin, sarkan başka her zaman' if 'en yakınına aittir).

Buna başka bir 'çözüm', Ayrıştırıcı İfade Dilbilgisi (PEG) kullanmaktır: Bu, yukarıda kullanılan BNF dilbilgisine benzer, ancak belirsizlik durumunda, her zaman 'ilkini seç'. Elbette, bu sorunu gerçekten 'çözmez', ancak belirsizliğin gerçekte var olduğunu gizleyin: Son kullanıcılar ayrıştırıcının hangi seçimi yaptığını bilemeyebilir ve bu beklenmedik sonuçlara yol açabilir.

Dilbilginizin herhangi bir belirsizliğe sahip olup olmadığını bilmek neden genel olarak imkansızdır da dahil olmak üzere, bu yazıdan çok daha derinlemesine olan daha fazla bilgi: Bu bağlamda, LL ve LR bağlamındaki harika blog makalesidir : Neden ayrıştırma? araçlar zor . Bunu gerçekten tavsiye ederim; Şu anda bahsettiğim her şeyi anlamamda bana çok yardımcı oldu.

50 yıllık araştırma

Ama hayat devam ediyor. Sonlu durum otomatları olarak uygulanan 'normal' LR stili ayrıştırıcıların çoğu zaman program boyutunda sorun olan binlerce duruma + geçişe ihtiyaç duyduğu ortaya çıktı. Böylece, Basit LR (SLR) ve LALR (Önden Görünümlü LR) gibi değişkenler , otomatiği küçültmek için diğer teknikleri birleştiren, ayrıştırıcı programların disk ve bellek alanını azaltan yazılmıştır.

Ayrıca, yukarıda belirtilen belirsizlikleri çözmenin bir başka yolu, belirsizlik durumunda, her iki seçeneğin de tutulduğu ve ayrıştırıldığı genelleştirilmiş tekniklerin kullanılmasıdır : 'doğru' olanı), her ikisinin de doğru olması durumunda ikisini (ve bu şekilde belirsizlik olduğunu göstererek) geri döndürmenin yanı sıra.

İlginç bir şekilde, Genelleştirilmiş LR algoritması tanımlandıktan sonra, benzer şekilde hızlı olan belirsiz gramerler için $ O (n ^ 3) $ zaman karmaşıklığı, $ O (n) olan Genelleştirilmiş LL ayrıştırıcılarını uygulamak için benzer bir yaklaşımın kullanılabileceği ortaya çıktı. Basit (LA) bir LR ayrıştırıcısından daha fazla defter tutma olmasına rağmen, daha net bir gramer faktörü anlamına gelse de, tamamen net olmayan dilbilgileri yazmak ve hata ayıklamak için.

Ayrıştırıcı Kombinatorler, Ayrıştırıcı Jeneratörler

Yani, bu uzun açıklama ile, şimdi sorunun özüne varıyoruz:

Ayrıştırıcı Birleştiricileri ve Ayrıştırıcı Jeneratörleri arasındaki fark nedir ve biri diğerinde ne zaman kullanılmalıdır?

Onlar gerçekten farklı türde canavarlar:

Ayrıştırıcı Birleştiriciler oluşturuldu, çünkü insanlar yukarıdan aşağıya ayrıştırıcı yazıyorlardı ve bunların çoğunun ortak olduğunu biliyordu .

Ayrıştırma Jeneratörleri , insanlar LL stili ayrıştırıcıların (yani LR tarzı ayrıştırıcılar) el ile yapması çok zor olan problemleri olmayan ayrıştırıcılar oluşturmak istedikleri için yaratıldı. Yaygın olanlar arasında (LA) LR) uygulayan Yacc / Bison bulunur.

İlginçtir, günümüzde manzara biraz karışmıştır:

  • GLL algoritmasıyla çalışan Ayrıştırıcı Kombinatorlerini yazmak , klasik LL tarzı ayrıştırıcıların sahip olduğu belirsizlik sorunlarını çözerken, her türlü yukarıdan aşağıya ayrıştırma kadar okunabilir / anlaşılabilir olmak mümkündür.

  • Ayrıştırma Jeneratörleri LL stili ayrıştırıcılar için de yazılabilir. ANTLR , tam olarak bunu yapar ve klasik LL tarzı ayrıştırıcıların sahip olduğu belirsizlikleri çözmek için diğer sezgisel taramaları (Adaptif LL (*)) kullanır.

Genel olarak, bir LR ayrıştırıcı üreteci oluşturmak ve ve grameriniz üzerinde çalışan bir (LA) LR tarzı ayrıştırıcı üreteci hatalarını ayıklamak, orijinal dilbilginizin 'iç-dış' LR biçimine çevrilmesi nedeniyle zordur. Öte yandan, Yacc / Bison gibi araçlar optimizasyonlar uzun yıllar vardı ve birçok kişi artık olarak kabul edebileceğimiz anlamına vahşi, kullanım çok gördük ayrıştırma yapmak için yolda ve yeni yaklaşımlara doğru şüpheciler.

Hangisini kullanmanız gerektiği, gramerinizin ne kadar zor olduğuna ve ayrıştırıcının ne kadar hızlı olması gerektiğine bağlıdır. Dilbilgisine bağlı olarak, bu tekniklerden biri (/ farklı tekniklerin uygulamaları) daha hızlı olabilir, daha küçük bir bellek ayak izine sahip olabilir, daha küçük bir disk ayak izine sahip olabilir veya diğerlerinden daha fazla genişletilebilir veya daha kolay hata ayıklanabilir. Kilometreniz değişebilir .

Yan not: Sözlüksel Analiz konusunda.

Sözlüksel Analiz hem Ayrıştırıcı Kombinatorler hem de Ayrıştırıcı Jeneratörleri için kullanılabilir. Buradaki fikir, kaynak kodunuz üzerinden ilk geçişi gerçekleştiren, örneğin beyaz boşlukları, yorumları vb. dilinizi oluşturan farklı unsurlar kaba şekilde.

Temel avantaj, bu ilk adımın gerçek ayrıştırıcıyı çok daha basit hale getirmesidir (ve bu nedenle daha hızlı olması nedeniyle). Başlıca dezavantajı, ayrı bir çeviri adımına sahip olmanız ve örneğin satır ve sütun numaralarıyla hata bildirimi, beyaz boşluğun kaldırılması nedeniyle zorlaşıyor olmasıdır.

Sonunda bir lexer 'sadece' başka bir ayrıştırıcıdır ve yukarıdaki tekniklerden herhangi biri kullanılarak uygulanabilir. Sadeliği nedeniyle, genellikle ana ayrıştırıcıdan başka teknikler kullanılır ve örneğin ekstra 'lexer jeneratörler' vardır.


Tl; Dr:

İşte çoğu durumda geçerli olan bir akış şeması: görüntü tanımını buraya girin


@Sjoerd Gerçekten de çok zor bir metin olduğu anlaşılan bir metin. Son paragrafı daha net hale getirmenin bir yolunu biliyorsanız, hepiniz kulaklarım olur: "Hangisini kullanmanız gerektiği, dilbilginizin ne kadar zor olduğuna ve ayrıştırıcının ne kadar hızlı olması gerektiğine bağlıdır. Dilbilgisine bağlı olarak, bu tekniklerden biri (/ farklı tekniklerin uygulamaları) daha hızlı olabilir, daha küçük bir bellek ayak izine sahip olabilir, daha küçük bir disk ayak izine sahip olabilir veya diğerlerinden daha fazla hata ayıklamak için daha genişletilebilir veya daha kolay olabilir. Mileage May Vary. "
Qqwy

1
Diğer cevaplar hem daha kısa hem de çok daha net ve cevaplamada çok daha iyi bir iş çıkarıyor.
Sjoerd

1
@Bu cevabı yazmamın sebebi, diğer cevapların ya problemi basitleştirdiği, kısmi bir cevabı tam cevap olarak sunması ve / veya fıkra yanıltma tuzağına düşmesiydi . Yukarıdaki cevap, Jörg W Mittag, Thomas Killian ve ben, ne hakkında konuştuklarını anladıktan ve önceden bilgi almadan kabul ettikten sonra soru yorumlarında bulunduk.
Qqwy

Her durumda, soruya bir tl; dr akış çizelgesi ekledim. Bu sizi tatmin ediyor mu, @Sjoerd?
Qqwy

2
Ayrıştırıcı Birleştiriciler, gerçekten kullanmadığınızda, sorunu gerçekten çözmekte başarısız olurlar. Her şeyden daha fazla birleştirici |var, hepsi bu. Yeniden yazmak için exprdoğru olan daha da özlü olanıdır expr = term 'sepBy' "+"(burada tek tırnak işaretleri bir işlev ekini döndürmek için backtick'lerin yerini almaktadır, çünkü mini-markdown karakterin kaçması yoktur). Daha genel durumda, chainBybirleştirici de var. PC'lere uygun olmayan bir örnek olarak basit bir ayrıştırma görevi bulmanın zor olduğunu biliyorum, ancak bu onların lehine güçlü bir argüman.
Steven Armstrong

8

Sözdizimi hatasından arındırılmış olduğu veya sözdizimsel doğrulukta genel bir başarılı / başarısız olduğu tamamlanan giriş için, ayrıştırıcı birleştiricilerin, özellikle işlevsel programlama dillerinde çalışması çok daha kolaydır. Bunlar, bulmaca programlama, veri dosyalarını okuma, vb. Durumlardır.

Ayrıştırıcı jeneratörlerin karmaşıklığını eklemek istemenizi sağlayan özellik hata mesajlarıdır. Kullanıcıyı bir çizgiye ve sütuna yönlendiren hata mesajları istersiniz ve umarım bir insan tarafından da anlaşılabilir. Bunu doğru şekilde yapmak çok fazla kod gerektirir ve antlr gibi daha iyi ayrıştırıcı jeneratörler bu konuda size yardımcı olabilir.

Bununla birlikte, otomatik üretim sizi yalnızca şu ana kadar elde edebilir ve çoğu ticari ve uzun ömürlü açık kaynaklı derleyiciler ayrıştırıcılarını el ile yazabilir. Bunu yapmakta kendinizi rahat hissederseniz, bu soruyu sormazsınız, bu yüzden ayrıştırıcı jeneratörü ile gitmeyi öneririm.


2
Cevabınız için teşekkür ederim! Bir Parser Generator kullanarak okunabilir hata mesajları oluşturmak bir Parser Combinator'den daha neden daha kolay olsun? (Özellikle hangi uygulamadan bahsettiğimize bakılmaksızın ) Örneğin, hem Parsec hem de Spirit'in satır + sütun bilgileri de dahil olmak üzere hata mesajları basma işlevi içerdiğini biliyorum, bu yüzden bunu Ayrıştırıcı Birleştiriciler'de de yapmak kesinlikle mümkün görünüyor.
Qqwy

Ayrıştırıcı birleştiricilerle hata iletileri yazdıramazsınız, karışıma hata iletileri gönderdiğinizde avantajları daha az belirgindir. Her iki yöntemi de kullanarak nispeten karmaşık bir dilbilgisi yapın ve ne demek istediğimi anlayın.
Karl Bielefeldt,

Bir Parser Combinator ile, tanım gereği bir hata durumunda alabileceğiniz tek şey "Bu noktada başlayarak, hiçbir yasal girdi bulunamadı" dır. Bu size gerçekten neyin yanlış olduğunu söylemez. Teorik olarak, bu noktada çağrılan bireysel ayrıcılar size ne beklediğini ve DIDN'nin bulamadığını söyleyebilir, ancak tek yapabileceğiniz şey, bir looooooong hata mesajı oluşturmak için tüm bunları yazdırmaktır.
John R. Strohm

1
Ayrıştırıcı jeneratörler dürüst olmak gerekirse, iyi hata mesajlarıyla da tam olarak bilinmemektedir.
Miles Rout,

Varsayılan olarak değil, hayır, ancak iyi hata mesajları eklemek için daha uygun kancalara sahipler.
Karl Bielefeldt,

4

Son zamanlarda ANTLR ayrıştırma jeneratörünün sağlayıcılarından Sam Harwell şunları yazdı :

[Birleştiricilerin] ihtiyaçlarımı karşılamadığını öğrendim:

  1. ANTLR bana belirsizlikler gibi şeyleri yönetmek için araçlar sağlar. Geliştirme sırasında bana belirsiz ayrıştırma sonuçları gösterebilecek araçlar var, böylece gramerdeki bu belirsizlikleri ortadan kaldırabilirim. Çalışma zamanında, kod tamamlama gibi özelliklerde daha doğru sonuçlar elde etmek için IDE'deki eksik girdiden kaynaklanan belirsizlikten yararlanabiliyorum.
  2. Uygulamada ayrıştırıcı birleştiricilerin performans hedeflerimi yerine getirmek için uygun olmadığını buldum. Bunun bir kısmı geri dönüyor
  3. Özetleme, kod tamamlama ve akıllı girinti gibi özellikler için ayrıştırma sonuçları kullanıldığında, bu sonuçların doğruluğunu etkilemek için gramerdeki ince değişikliklerin yapılması kolaydır. ANTLR, bu tür uyumsuzlukları derleme hatalarına dönüştürebilen, türlerin başka türlü derlenebileceği durumlarda bile araçlar sağlar. Güven prototipiyle, IDE'yi oluşturan tüm ek kodların baştan beri yeni özellik için tam bir deneyim sağlayacağını bilerek dilbilgisini etkileyen yeni bir dil özelliği olabilir. ANTLR 4 çatalım (C # hedefi dayanıyor) bu özelliği sağlamaya çalıştığını bile bildiğim tek araç.

Temel olarak, ayrıştırıcı birleştiriciler oynamak için harika bir oyuncaktır, ancak ciddi işler yapmak için kesilmezler.


3

Karl'ın belirttiği gibi, ayrıştırıcı üreticiler daha iyi hata raporlama eğilimindedir. Ek olarak:

  • Oluşturulan kod sözdizimi için özelleştirilebildiğinden ve görünüm için atlama tabloları oluşturduğundan daha hızlı olma eğilimindedirler.
  • belirsiz sözdizimini tanımlamak, sola özyinelemeyi kaldırmak, hata dallarını doldurmak vb.
  • özyinelemeli tanımları daha iyi ele alma eğilimindedirler.
  • jeneratörler daha uzun süredir bulundukları ve sizin için daha fazla kazan yaptıkları için daha sağlam olma eğilimindedirler.

Öte yandan, birleştiricilerin kendi avantajları vardır:

  • bunlar koddadır, bu nedenle sözdiziminiz çalışma süresinde değişiyorsa, şeyleri daha kolay bir şekilde değiştirebilirsiniz.
  • Bağlanması ve tüketilmesi daha kolay olma eğilimindedirler (ayrıştırıcı üreticilerin çıktısı kullanımı çok genel ve garip olma eğilimindedir).
  • onlar kodda olduğundan, grameriniz beklediğiniz şeyi yapmadığında hata ayıklamak biraz daha kolay olma eğilimindedir.
  • diğer kodlar gibi çalıştıkları için daha sığ bir öğrenme eğrisi yaşama eğilimindedirler. Ayrıştırma jeneratörleri, işleri yürütmeyi öğrenmek için kendi tuhaflıklarına sahip olma eğilimindedir.

Ayrıştırıcı jeneratörler, gerçek dünyada kullanılan elle yazılmış LL özyinelemeli ayrıştırıcılara göre korkunç hata raporlama eğilimindedir. Ayrıştırıcı jeneratörler nadiren harika teşhisler eklemek için gerekli durum tablosu geçiş kancaları sunarlar. Bu nedenle hemen hemen her gerçek derleyici ayrıştırıcı birleştiricileri veya ayrıştırıcı jeneratörlerini kullanmaz. LL özyinelemeli çözüm ortakları, "temiz" PC'ler / PG'ler kadar olmasa da, daha kullanışlıdırlar.
dhchdhd
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.