GCC ve Clang ayrıştırıcıları gerçekten el yazısı mı?


90

GCC ve LLVM-Clang kullandığınız görülüyor el yazısı özyinelemeli kökenli ayrıştırıcıları ve değil üretilen Bison-Flex tabanlı, aşağıdan yukarıya ayrıştırma makinesi.

Buradaki biri lütfen durumun bu olduğunu onaylayabilir mi? Ve eğer öyleyse, ana akım derleyici çerçeveleri neden el yazısı ayrıştırıcıları kullanıyor?

Güncelleme : bu konuyla ilgili ilginç blog burada


27
Neredeyse tüm ana akım derleyiciler el yazısıyla yazılmış ayrıştırıcılar kullanıyor. Bunda sorun ne?
SK-logic

2
performansa ihtiyacınız varsa bunu (yarı) manuel olarak yapmanız gerekir.
Gene Bushuyev

15
Ve sadece performans değil - daha iyi hata mesajları, kurtarma yeteneği vb.
SK-logic

Peki ya MS VisualStudio? açık kaynaklı olmasa da, MS'den biri kendisinin de elle yazılmış özyinelemeli iniş ayrıştırıcısı kullandığını doğrulayabilir mi?
OrenIshShalom

3
@GeneBushuyev, GCC wiki'den: "... Zamanlamalar % 1,5 hızlanma göstermesine rağmen , ana faydalar gelecekteki geliştirmeleri kolaylaştırıyor ..." bu hızlanma oldukça marjinal görünüyor ...
OrenIshShalom

Yanıtlar:


78

Evet:

  • GCC bir zamanlar bir yacc (bizon) ayrıştırıcı kullanılır, ancak 3.x serisinde bir noktada elle yazılmış bir özyinelemeli kökenli ayrıştırıcı ile değiştirildi: bkz http://gcc.gnu.org/wiki/New_C_Parser için ilgili yama gönderimlerine bağlantılar.

  • Clang ayrıca elle yazılmış özyinelemeli bir iniş ayrıştırıcı kullanır: http://clang.llvm.org/features.html'nin sonuna yakın "C, Objective C, C ++ ve Objective C ++ için tek bir birleşik ayrıştırıcı" bölümüne bakın .


3
Bu ObjC, C ve C ++ 'nın LL (k) Gramerlerine sahip olduğu anlamına mı geliyor?
Lindemann

47
Hayır: Üçünden en basiti olan C bile belirsiz bir gramere sahiptir. Örneğin, foo * bar;bir çarpma ifadesi (sonuç kullanılmadan) veya barişaretçi türüne sahip bir değişkenin bildirimi olarak ayrıştırılabilir foo. Hangisi doğru olan bir olmadığına bağlıdır typedefiçin foolookahead herhangi miktarı ile belirlenebilir şey değildir zamanda en kapsamı içindedir. Ancak bu, yinelemeli iniş ayrıştırıcısının bunun üstesinden gelmek için bazı çirkin ekstra makinelere ihtiyaç duyduğu anlamına gelir.
Matthew Slattery

9
Ampirik kanıtlardan, C ++ 11, C ve Objective C'nin bir GLR ayrıştırıcısının kullanabileceği bağlamdan bağımsız gramerlere sahip olduğunu doğrulayabilirim.
Ira Baxter

2
Bağlam duyarlılığı ile ilgili olarak, bu cevap hiçbirini iddia etmez: bu dilleri ayrıştırmanın muhtemelen Turing-tamamlanmış olduğu.
Ioannis Filippidis

107

C'nin ayrıştırılmasının zor olduğunu ve C ++ 'nın esasen imkansız olduğunu söyleyen bir halk teoremi var.

Doğru değil.

Doğru olan, C ve C ++ 'nın LALR (1) ayrıştırıcılarını kullanarak ayrıştırma makinesini kırmadan ve sembol tablosu verilerinde dolaşmadan ayrıştırılmasının oldukça zor olmasıdır. Aslında GCC, YACC ve bunun gibi ek bilgisayar korsanlığı kullanarak onları ayrıştırırdı ve evet, bu çirkindi. Artık GCC, el yazısı ayrıştırıcıları kullanıyor, ancak yine de sembol tablosu korsanlığı ile. Clang çalışanları asla otomatik ayrıştırıcı üreteçleri kullanmayı denemedi; AFAIK Clang ayrıştırıcısı her zaman elle kodlanmış özyinelemeli iniş olmuştur.

Doğru olan, C ve C ++ 'nın daha güçlü otomatik olarak oluşturulmuş ayrıştırıcılarla, örneğin GLR ayrıştırıcılarla ayrıştırılmasının nispeten daha kolay olduğu ve herhangi bir hacklemeye ihtiyacınız olmadığıdır. Elsa C ++ ayrıştırıcı bunun bir örneğidir. Bizim C ++ Ön Uç başka (olarak tüm "derleyici" ön uçları vardır, GLR oldukça harika ayrıştırma teknolojisidir) 'dir.

C ++ ön ucumuz GCC'ler kadar hızlı değil ve kesinlikle Elsa'dan daha yavaş; Onu dikkatlice ayarlamak için çok az enerji harcadık çünkü daha acil sorunlarımız var (yine de milyonlarca satır C ++ kodunda kullanıldı). Elsa muhtemelen daha genel olduğu için GCC'den daha yavaştır. Bugünlerde işlemci hızları göz önüne alındığında, bu farklılıklar pratikte çok önemli olmayabilir.

Ancak bugün yaygın olarak dağıtılan "gerçek derleyiciler", köklerini 10 veya 20 yıl veya daha eski derleyicilere dayandırmaktadır. O zaman verimsizlikler çok daha önemliydi ve hiç kimse GLR ayrıştırıcılarını duymamıştı, bu yüzden insanlar nasıl yapacaklarını bildiklerini yaptılar. Clang kesinlikle daha yenidir, ancak daha sonra halk teoremleri "ikna edebilirliklerini" uzun süre korurlar.

Artık bunu bu şekilde yapmak zorunda değilsin. GLR ve diğer bu tür ayrıştırıcıları, derleyici bakımında bir gelişme ile, ön uç olarak çok makul bir şekilde kullanabilirsiniz.

Ne olduğunu doğrudur, Mahallenin iyi derleyici'nın davranışını eşleşen bir dilbilgisi alma zor olmasıdır. Neredeyse tüm C ++ derleyicileri orijinal standardı (çoğu) uygularken, aynı zamanda çok sayıda karanlık köşe uzantısına da sahiptirler, örneğin MS derleyicilerinde DLL özellikleri, vb. Güçlü bir ayrıştırma motorunuz varsa, zamanınızı elde etmeye çalışarak geçirebilirsiniz. ayrıştırıcı oluşturucunuzun sınırlamalarına uyması için gramerinizi esnetmeye çalışmak yerine, son dilbilgisini gerçeğe uyacak şekilde kullanın.

DÜZENLEME Kasım 2012: Bu cevabı yazdığımızdan beri, C ++ ön ucumuzu ANSI, GNU ve MS varyant lehçeleri dahil olmak üzere tam C ++ 11'i işleyecek şekilde geliştirdik. Çok fazla ekstra şey olsa da, ayrıştırma motorumuzu değiştirmemize gerek yok; sadece gramer kurallarını gözden geçirdik. Anlamsal analizi değiştirmek zorunda kaldık ; C ++ 11 anlamsal olarak çok karmaşıktır ve bu çalışma ayrıştırıcının çalışmasını sağlama çabasını batırır.

DÜZENLEME Şubat 2015: ... artık tam C ++ 14'ü işliyor. ( Basit bir kod bitinin GLR ayrıştırmaları için c ++ kodundan insan tarafından okunabilir AST alın ve C ++ 'ın meşhur "en can sıkıcı ayrıştırması" na bakın).

Nisan 2017 DÜZENLEME: Artık (taslak) C ++ 17'yi işliyor.


6
PostScript: Dilbilgisini satıcıların gerçekte yaptıklarıyla eşleşecek şekilde elde etmek daha zor olduğu gibi, farklı satıcının C ++ 11 kılavuzuna ilişkin yorumuyla eşleşecek ad ve tür çözümlemesi almak daha da zor çünkü sahip olduğunuz tek kanıt, biraz derleyen programlar. onları bulabilirseniz farklı şekilde. C ++ 11 için Ağustos 2013 itibariyle bunu büyük ölçüde geçtik, ancak C ++ komitesinde C biçiminde daha büyük (ve deneyimden, daha kafa karıştırıcı) bir standart üretmeye cehennem gibi görünen biraz umutsuzluğa kapılıyorum. ++ 1y.
Ira Baxter

5
Gerçekten bilmek istiyorum: Bu foo * bar;belirsizlikle nasıl başa çıkıyorsunuz ?
Martin

14
@Martin: Ayrıştırıcımız her iki şekilde ayrıştırır , çocukları alternatif çözümlemeler olan özel "belirsizlik düğümleri" içeren bir ağaç oluşturur; Çocuklar çocuklarını azami ölçüde paylaşıyorlar, böylece sonunda ağaç yerine bir DAG elde ediyoruz. Ayrıştırma tamamlandıktan sonra , DAG (bilmiyorsanız "ağacı yürüyün ve bir şeyler yapın" için süslü bir ad) üzerinden bildirilen tüm tanımlayıcıların türlerini hesaplayan bir öznitelik gramer değerlendiricisi (AGE) çalıştırırız. ...
Ira Baxter

12
... Belirsiz çocukların ikisi de tip tutarlı olamaz; Mantıklı bir şekilde yazılamayan belirsiz bir çocuğu keşfetme üzerindeki YAŞ, basitçe onu siler. Geriye iyi tipli çocuklar kaldı; böylece, "foo bar " ın hangi ayrıştırdığını belirledik ; doğru. Bu numara, C ++ 11'in gerçek lehçeleri için oluşturduğumuz gerçek gramerlerde bulunan her türlü çılgın belirsizlik için işe yarıyor ve * adlar için ayrıştırmayı anlamsal analizden tamamen ayırıyor. Bu temiz ayrım, yapılacak çok daha az mühendislik çalışması anlamına gelir (hata ayıklanacak karışıklıklar yoktur). Daha fazla tartışma için stackoverflow.com/a/1004737/120163 sayfasına bakın .
Ira Baxter

3
@TimCas: Aslında, doğru yapmak çok zor olan dil sözdizimi (ve anlambilim) tasarlamanın aptalca aptallığından korkarak sizinle birlikteyim (evet, C ++ dili burada kötü acı çekiyor). Dil tasarım komitelerinin sözdizimi tasarlamasını, böylece daha basit ayrıştırma teknolojilerinin işe yaramasını ve dilin anlambilimini açıkça tanımlamasını ve bazı anlamsal analiz araçlarıyla kontrol etmesini diliyorum. Ne yazık ki, dünya öyle görünmüyor. Bu yüzden, inşa etmeniz gerekeni olabildiğince iyi inşa ettiğiniz ve garipliğe rağmen hayata devam ettiğiniz görüşüne sahibim.
Ira Baxter

31

Clang'ın ayrıştırıcısı, diğer birçok açık kaynaklı ve ticari C ve C ++ ön uçları gibi, elle yazılmış özyinelemeli bir ayrıştırıcıdır.

Clang, çeşitli nedenlerle özyinelemeli bir ayrıştırıcı kullanır:

  • Performans : elle yazılmış bir ayrıştırıcı, hızlı bir ayrıştırıcı yazmamızı, sıcak yolları gerektiği gibi optimize etmemizi sağlar ve bu performansın kontrolü her zaman bizdedir. Hızlı bir ayrıştırıcıya sahip olmak, Clang'ın "gerçek" ayrıştırıcıların tipik olarak kullanılmadığı diğer geliştirme araçlarında kullanılmasına izin verdi, örneğin, sözdizimi vurgulama ve bir IDE'de kod tamamlama.
  • Tanılama ve hata kurtarma : elle yazılmış bir özyinelemeli ayrıştırıcıyla tam denetimde olduğunuz için, yaygın sorunları algılayan ve mükemmel tanılama ve hata kurtarma sağlayan özel durumlar eklemek kolaydır (örneğin, bkz. Http: //clang.llvm .org / features.html # expressivediags ) Otomatik olarak oluşturulan ayrıştırıcılarla, oluşturucunun yetenekleriyle sınırlısınız.
  • Basitlik : özyinelemeli ayrıştırıcıların yazılması, anlaşılması ve hata ayıklaması kolaydır. Ayrıştırıcıyı genişletmek / geliştirmek için bir ayrıştırma uzmanı olmanıza veya yeni bir araç öğrenmenize gerek yoktur (bu özellikle açık kaynaklı bir proje için önemlidir), ancak yine de harika sonuçlar alabilirsiniz.

Genel olarak, bir C ++ derleyicisi için bu çok da önemli değil: C ++ 'nın ayrıştırma kısmı önemsiz değil, ama yine de daha kolay kısımlardan biri, bu yüzden basit tutmak için para ödüyor. Anlamsal analiz - özellikle ad arama, başlatma, aşırı yükleme çözümleme ve şablon somutlaştırma - büyüklük sıralamaları ayrıştırmadan daha karmaşıktır. Kanıt istiyorsanız, kodun dağıtımına bakın ve Clang'ın "Sema" bileşenine (anlamsal analiz için) karşı "Ayrıştırma" bileşenine (ayrıştırma için) bakın.


4
Evet, anlamsal analiz çok daha zordur. C ++ 11 gramerimizi oluşturan 4000 satırlık gramer kuralımız ve yukarıdaki "semantik analizler" Doub listeleri için yaklaşık 180.000 satırlık öznitelik dilbilgisi kodumuz ve 100.000 satırlık destekleyici kodumuz var. Yanlış adımdan başlarsanız yeterince zor olsa da, gerçekten sorun ayrıştırmak değildir.
Ira Baxter

1
El ile yazılmış ayrıştırıcıların hata raporlama / kurtarma için mutlaka daha iyi olduğundan emin değilim . Görünüşe göre insanlar bu tür ayrıştırıcılara pratikte otomatik ayrıştırıcı üreteçleri tarafından üretilen ayrıştırıcıları geliştirmek yerine daha fazla enerji harcıyorlar. Konuyla ilgili oldukça iyi araştırmalar var gibi görünüyor; bu özel makale gerçekten gözüme çarptı: MG Burke, 1983, LR ve LL sözdizimsel hata teşhisi ve düzeltmesi için pratik bir yöntem, Doktora tezi, Bilgisayar Bilimleri Bölümü, New York Üniversitesi, Bkz. archive.org/details/practicalmethodf00burk
Ira Baxter

1
... bu düşünce zincirine devam etmek: daha iyi teşhis için özel durumları kontrol etmek için el yapımı ayrıştırıcınızı değiştirmeye / genişletmeye / özelleştirmeye istekli iseniz, mekanik olarak oluşturulmuş bir ayrıştırıcının daha iyi tanılamalarına eşit yatırım yapmaya istekli olmalısınız. Manuel olan için kodlayabileceğiniz herhangi bir özel ayrıştırma için, mekanik olan için de bir çeki kodlayabilirsiniz (ve (G) LR ayrıştırıcıları için, bunu indirgemelerin anlamsal kontrolleri olarak hemen hemen yapabilirsiniz). İştah açıcı görünmediği ölçüde, kişi sadece tembellik ediyor, ancak bu mekanik olarak oluşturulmuş ayrıştırıcılar IMHO'nun bir suçlaması değil.
Ira Baxter

8

gcc'nin ayrıştırıcısı elle yazılmıştır. . Ben clang için de aynı şeyden şüpheleniyorum. Bu muhtemelen birkaç nedenden dolayıdır:

  • Performans : Özel göreviniz için elle optimize ettiğiniz bir şey, neredeyse her zaman genel bir çözümden daha iyi performans gösterir. Soyutlamanın genellikle bir performans vuruşu vardır
  • Zamanlama : En azından GCC durumunda, GCC birçok ücretsiz geliştirici aracından önce gelir (1987'de çıktı). O zamanlar, FSF'deki insanlar için bir öncelik olacağını düşündüğüm ücretsiz bir yacc sürümü yoktu.

Bu muhtemelen bir "burada icat edilmedi" sendromu vakası değil, daha çok "ihtiyacımız olan şey için özel olarak optimize edilmiş hiçbir şey yoktu, bu yüzden kendimizinkini yazdık" şeklinde.


15
1987'de yacc'nin ücretsiz sürümü yok mu? Yacc'nin 70'lerde Unix altında ilk kez teslim edildiğinde ücretsiz sürümleri olduğunu düşünüyorum. Ve IIRC (diğer afiş aynı görünüyor), GCC kullanılan bir YACC tabanlı ayrıştırıcı olması. Bunu değiştirmenin bahanesinin daha iyi hata bildirimi almak olduğunu duydum.
Ira Baxter

7
El yazısı ayrıştırıcıdan iyi hata mesajları oluşturmanın genellikle daha kolay olduğunu eklemek isterim.
Dietrich Epp

1
Zamanlama konusundaki düşünceniz yanlış. GCC eskiden YACC tabanlı ayrıştırıcıya sahipti, ancak bu daha sonra el yazısı özyinelemeli bir iniş ayrıştırıcıyla değiştirildi.
Tommy Andersen

7

Garip cevaplar var!

C / C ++ gramerler bağlamdan bağımsız değildir. Foo * çubuğu nedeniyle bağlama duyarlıdırlar; belirsizlik. Foo'nun bir tür olup olmadığını anlamak için bir daktilo listesi oluşturmalıyız.

Ira Baxter: GLR konunun amacını anlamıyorum. Neden belirsizlikler içeren bir ayrıştırma ağacı inşa edelim? Ayrıştırma, belirsizlikleri çözmek, sözdizimi ağacını oluşturmak anlamına gelir. Bu belirsizlikleri ikinci bir geçişte çözersiniz, bu yüzden bu daha az çirkin olmaz. Benim için çok daha çirkin ...

Yacc bir LR (1) ayrıştırıcı üreticisidir (veya LALR (1)), ancak içeriğe duyarlı olacak şekilde kolayca değiştirilebilir. Ve içinde çirkin hiçbir şey yok. Yacc / Bison, C dilini ayrıştırmaya yardımcı olmak için oluşturulmuştur, bu nedenle muhtemelen bir C ayrıştırıcısı oluşturmak için en çirkin araç değildir ...

GCC 3.x'e kadar C ayrıştırıcısı, ayrıştırma sırasında oluşturulan typedef tablosu ile yacc / bison tarafından üretilir. "İn parse" typedef tablo oluşturma ile, C dilbilgisi yerel olarak bağlamdan bağımsız ve ayrıca "yerel olarak LR (1)" olur.

Şimdi, Gcc 4.x'te, özyinelemeli bir iniş ayrıştırıcısıdır. Gcc 3.x'teki ile tamamen aynı ayrıştırıcıdır, hala LR (1) ve aynı gramer kurallarına sahiptir. Aradaki fark, yacc ayrıştırıcısının elle yeniden yazılmış olması, shift / küçültme artık çağrı yığınında gizli ve "state454: if (nextsym == '(') goto state398" olmamasıdır. Gcc 3.x yacc's ayrıştırıcı, bu nedenle yama yapmak, hataları işlemek ve daha güzel mesajlar yazdırmak ve ayrıştırma sırasında sonraki derleme adımlarından bazılarını gerçekleştirmek daha kolaydır. Bir gcc noob için çok daha az "okunması kolay" kod fiyatına.

Neden yacc'den özyinelemeli inişe geçtiler? Çünkü yacc'nin C ++ 'yı ayrıştırmaktan kaçınmak ve GCC çok dilli derleyici olmayı hayal ettiğinden, yani derleyebileceği farklı diller arasında maksimum kod paylaşabilir. C ++ ve C ayrıştırıcısının aynı şekilde yazılmasının nedeni budur.

C ++, C'den daha zordur çünkü C gibi "yerel olarak" LR (1) değildir, hatta LR (k) değildir. func<4 > 2>Hangisinin 4> 2 ile somutlaştırılmış bir şablon işlevi olduğuna bakın , yani func<4 > 2> olarak okunması gerekir func<1>. Bu kesinlikle LR (1) değildir. Şimdi düşünün func<4 > 2 > 1 > 3 > 3 > 8 > 9 > 8 > 7 > 8>. Bu, yinelemeli bir inişin belirsizliği birkaç işlev çağrısı fiyatına kolayca çözebileceği yerdir (parse_template_parameter, belirsiz ayrıştırıcı işlevidir. Parse_template_parameter (17tokens) başarısız olursa, parse_template_parameter (15tokens), parse_template_parameter (13tokens) ... işe yarıyor).

Yacc / bison özyinelemeli alt dilbilgilerini eklemenin neden mümkün olmadığını bilmiyorum, belki bu gcc / GNU ayrıştırıcı geliştirmede bir sonraki adım olacak?


9
"benim için çok daha çirkin". Size söyleyebileceğim şey, GLR ve gecikme belirsizlik çözümünü kullanan bir üretim kalitesi ayrıştırıcısının mühendisliğinin gerçekten küçük bir ekiple pratik olduğudur. Gördüğüm diğer tüm çözümler, LR ile çalışması için gerekli olan backflips ve hack'lere halk arasında yıllarca diş gıcırdatmayı içeriyor, yinelemeli iniş, adını siz koyun. Diğer birçok harika yeni ayrıştırma teknolojisini varsayabilirsiniz, ancak anlayabildiğim kadarıyla, bu noktada bu daha çok diş gıcırdatmaktır. Fikirler ucuzdur; infaz canım.
Ira Baxter

@IraBaxter: Sıçanlar! citeseerx.ist.psu.edu/viewdoc/…
Fizz

@Fizz: Karmaşık bir bilimsel programlama dili olan Fortress'in ayrıştırılması üzerine ilginç bir makale. Dikkat çekici birkaç şey söylediler: a) klasik ayrıştırıcı üreteçleri (LL (k), LALR (1)) zor gramerlerle başa çıkamıyorlar, b) GLR'yi denediler, ölçekle ilgili sorunları vardı ama geliştiriciler deneyimsizdi, bu yüzden yapmadılar tamamlandı [bu GLR'nin hatası değil] ve c) bir geri izleme (işlemsel) Packrat ayrıştırıcısı kullandılar ve daha iyi hata mesajları üretmek için çalışma da dahil olmak üzere ona çok çaba harcadılar. Onların "{| x || x ← mySet, 3 | x}" ayrıştırma örnekleriyle ilgili olarak, GLR'nin bunu gayet iyi yapacağına ve boşluk gerektirmediğine inanıyorum.
Ira Baxter

0

Görünüşe göre GCC ve LLVM-Clang el yazısıyla yazılmış özyinelemeli iniş ayrıştırıcıları kullanıyor ve makine tarafından üretilmiş, Bison-Flex tabanlı, aşağıdan yukarıya ayrıştırmayı değil.

Özellikle Bison, bazı şeyleri belirsiz bir şekilde ayrıştırmadan ve daha sonra ikinci bir geçiş yapmadan dilbilgisini idare edebileceğini sanmıyorum.

Haskell's Happy'nin C sözdizimi ile belirli bir sorunu çözebilen monadik (yani duruma bağlı) ayrıştırıcılara izin verdiğini biliyorum, ancak kullanıcı tarafından sağlanan bir durum monadına izin veren hiçbir C ayrıştırıcı üreteci bilmiyorum.

Teorik olarak, hata kurtarma, el yazısı ayrıştırıcı lehine bir nokta olabilir, ancak GCC / Clang ile olan deneyimim, hata mesajlarının özellikle iyi olmadığı yönünde olmuştur.

Performansa gelince - bazı iddialar asılsız görünüyor. Bir ayrıştırıcı üreteci kullanarak büyük bir durum makinesi oluşturmak, bunun bir şeye yol açması gerekir O(n)ve ayrıştırmanın çoğu araçta darboğaz olduğundan şüpheliyim.


3
Bu sorunun zaten çok kaliteli bir cevabı var, ne eklemeye çalışıyorsunuz?
tod
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.